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

package oracle.portal.provider.v1.http;

import java.io.*;
import java.util.*;

import javax.servlet.*;
import javax.servlet.http.*;

import oracle.webdb.utils.SimpleStringBuffer;
import oracle.portal.utils.v1.MultiByteDecoder;
import oracle.portal.utils.v1.MultiByteEncoder;
import oracle.portal.provider.v1.*;

/**
 * Wraps the servlet Request so we can "forward" requests to a servlet/jsp and
 * respond to that "forwarded" servlet with the proper paths.
 */
public class Servlet20RequestWrapper implements HttpServletRequest
{
    private String mVirtualPath;
    private String mRealPath;
    private Hashtable mAttributes = new Hashtable();
    private Hashtable mParameters = new Hashtable();
    private Hashtable mRawParameters = new Hashtable();
    private HttpServletRequest mRequest;
    private String mURLCharSet = HttpProviderConstants.HTTP_DEFAULT_CHARSET;

    private static final int READ_BUFFER_SIZE = 1023;


    /**
     * Constructs this <code>Servlet20RequestWrapper</code> object.
     *
     * @param sr  a {@link javax.servlet.http.HttpServletRequest}.
     */
    public Servlet20RequestWrapper(HttpServletRequest sr)
    {
        mRequest = sr;
        processRequestParameters();
    }

    /**
     * Constructs this <code>Servlet20RequestWrapper</code> object.
     *
     * @param pr  a {@link oracle.portal.provider.v1.PortletRenderRequest}.
     * @param vp  a server relative virtual path for this request.
     * @param rp  a file system relative real path for this request.
     */
    public Servlet20RequestWrapper(PortletRenderRequest pr, String vp,
                                   String rp)
    {
        this((HttpServletRequest)pr.getAttribute(HttpProvider.SERVLET_REQUEST));
        setPortletRenderRequest(pr);
        setVirtualPath(vp);
        setRealPath(rp);
    }

    /**
     * Constructs this <code>Servlet20RequestWrapper</code> object by including
     * a local parameter store. This parameter store must be a {@link
     * java.util.Hashtable} whose <code>keys</code> are {@link
     * java.lang.String} and <code>values</code> are a {@link java.util.Vector}
     * of {@link java.lang.String}s. The parameters held in the local store
     * will then be available, along with the {@link
     * javax.servlet.http.HttpServletRequest} parameters, from calls to the
     * {@link #getParameter(String)}, {@link #getParameterValues(String)} and
     * {@link #getParameterNames()} methods.
     *
     * @param pr  a {@link oracle.portal.provider.v1.PortletRenderRequest}.
     * @param vp  a server relative virtual path for this request.
     * @param rp  a file system relative real path for this request.
     * @param parameters - a {@link java.util.Hashtable} of local parameters.
     */
    public Servlet20RequestWrapper(PortletRenderRequest pr, String vp,
                                   String rp, Hashtable parameters)
    {
        this(pr, vp, rp);
        addLocalParameters(parameters);
    }

    /**
     * Sets a <code>PortletRenderRequest</code> whose attributes should be
     * combined with the attributes of the 'wrapped'
     * <code>HttpServletRequest</code>. Once set, the {@link
     * #getAttribute(String)} method will return the attribute of the given
     * name in the <code>PortletRenderRequest</code> in the absence of an
     * attribute of the given name in the 'wrapped'
     * <code>HttpServletRequest</code>.
     *
     * @param pr a {@link oracle.portal.provider.v1.PortletRenderRequest}.
     */
    public void setPortletRenderRequest(PortletRenderRequest pr)
    {
        addRenderRequestAttributes(pr);
    }

    /**
     * Overrides the server relative virtual path for this request.
     *
     * @param vp  a server relative virtual path for this request.
     */
    public void setVirtualPath(String vp)
    {
        mVirtualPath = vp;
    }

    /**
     * Overrides the file system relative real path for this request.
     *
     * @param rp  a file system relative real path for this request.
     */
    public void setRealPath(String rp)
    {
        mRealPath = rp;
    }

    /**
     * Returns the size of the request entity data, or -1 if not known.  Same
     * as the CGI variable CONTENT_LENGTH.
     */
    public int getContentLength()
    {
        return mRequest.getContentLength();
    }

    /**
     * Returns the Internet Media Type of the request entity data, or null if
     * not known. Same as the CGI variable CONTENT_TYPE.
     */
    public String getContentType()
    {
        return mRequest.getContentType();
    }

    /**
     * Returns the protocol and version of the request as a string of the form
     * <code>&lt;protocol&gt;/&lt;major version&gt;.&lt;minor
     * version&gt</code>.  Same as the CGI variable SERVER_PROTOCOL.
     */
    public String getProtocol()
    {
        return mRequest.getProtocol();
    }

    /**
     * Returns the scheme of the URL used in this request, for example "http",
     * "https", or "ftp".  Different schemes have different rules for
     * constructing URLs, as noted in RFC 1738.  The URL used to create a
     * request may be reconstructed using this scheme, the server name and
     * port, and additional information such as URIs.
     */
    public String getScheme()
    {
        return mRequest.getScheme();
    }

    /**
     * Returns the host name of the server that received the request.  Same as
     * the CGI variable SERVER_NAME.
     */
    public String getServerName()
    {
        return mRequest.getServerName();
    }

    /**
     * Returns the port number on which this request was received.  Same as the
     * CGI variable SERVER_PORT.
     */
    public int getServerPort()
    {
        return mRequest.getServerPort();
    }

    /**
     * Returns the IP address of the agent that sent the request.  Same as the
     * CGI variable REMOTE_ADDR.
     */
    public String getRemoteAddr()
    {
        return mRequest.getRemoteAddr();
    }

    /**
     * Returns the fully qualified host name of the agent that sent the
     * request. Same as the CGI variable REMOTE_HOST.
     */
    public String getRemoteHost()
    {
        return mRequest.getRemoteHost();
    }

    /**
     * Applies alias rules to the specified virtual path and returns the
     * corresponding real path, or null if the translation can not be performed
     * for any reason.  For example, an HTTP servlet would resolve the path
     * using the virtual docroot, if virtual hosting is enabled, and with the
     * default docroot otherwise.  Calling this method with the string "/" as
     * an argument returns the document root.
     *
     * @param path the virtual path to be translated to a real path
     */
    public String getRealPath(String path)
    {
        return mRequest.getRealPath(path);
    }

    /**
     * Returns an input stream for reading binary data in the request body.
     *
     * @see #getReader()
     * @exception IllegalStateException if getReader has been called on this
     * same request.
     * @exception IOException on other I/O related errors.
     */
    public ServletInputStream getInputStream() throws IOException
    {
        return mRequest.getInputStream();
    }

    /**
     * Returns a string containing the lone value of the specified parameter,
     * or null if the parameter does not exist. For example, in an HTTP servlet
     * this method would return the value of the specified query string
     * parameter. Servlet writers should use this method only when they are
     * sure that there is only one value for the parameter.  If the parameter
     * has (or could have) multiple values, servlet writers should use
     * getParameterValues. The local parameter store is checked for a matching
     * value first. If one exists, that value is returned. If a multiple valued
     * parameter name is passed as an argument and the parameter exists in the
     * local store, the first value in that local store is returned. Otherwise,
     * the return value is implementation dependent.
     *
     * @see #getParameterValues
     *
     * @param name the name of the parameter whose value is required.
     */
    public String getParameter(String name)
    {
        Vector params = (Vector)mParameters.get(name);

        // if a vector was not returned, parameter has not be decoded or it
        // does not exist.
        if (params == null)
        {
            params = decodeParameter(name);
            if (params == null || params.size() == 0)
            {
                return null;
            }
        }
        return (String)params.firstElement();
    }

    /**
     * Returns the values of the specified parameter for the request, including
     * those values found in the local store, as an array of strings, or null
     * if the named parameter does not exist. For example, in an HTTP servlet
     * this method would return the values of the specified query string, or
     * posted form, plus the values in the local store, as an array of strings.
     *
     * @param name the name of the parameter whose value is required.
     * @see javax.servlet.ServletRequest#getParameter
     */
    public String[] getParameterValues(String name)
    {
        Vector params = (Vector)mParameters.get(name);

        // if a vector was not returned, parameter has not be decoded or it
        // does not exist.
        if (params == null)
        {
            params = decodeParameter(name);
        }
        String[] returnArray = null;
        if (params != null && params.size() > 0)
        {
            returnArray = new String[params.size()];
            params.copyInto(returnArray);
        }
        return returnArray;
    }

    /**
     * Returns the parameter names for this request, plus the parameter names
     * in the local store, as an enumeration of strings, or an empty
     * enumeration if there are no parameters or the input stream is empty.
     * The input stream would be empty if all the data had been read from the
     * stream returned by the method getInputStream.
     */
    public Enumeration getParameterNames()
    {
        // make sure all the parameters are moved from the "raw" set to the
        // "decoded" set before returning the keys. If this is not done, the
        // list of names will be incomplete. This requires all the parameters
        // to be decoded, but since this method is being called, the caller
        // is most likely going to access all the parameters anyway.
        if (!mRawParameters.isEmpty())
        {
            Enumeration enum = mRawParameters.keys();
            String name;
            while (enum.hasMoreElements())
            {
                name = (String)enum.nextElement();
                decodeParameter(name);
            }
        }
        // now all parameters are transfered to the "decoded" set, return
        // the set of names.
        return mParameters.keys();
    }

    /**
     * Returns the value of the named attribute of the request, or null if the
     * attribute does not exist.  This method allows access to request
     * information not already provided by the other methods in this interface.
     * Attribute names should follow the same convention as package names.  The
     * following predefined attributes are provided.
     *
     * <TABLE BORDER>
     * <tr>
     *	<th>Attribute Name</th>
     *	<th>Attribute Type</th>
     *	<th>Description</th>
     * </tr>
     *
     * <tr>
     *	<td VALIGN=TOP>javax.net.ssl.cipher_suite</td>
     *	<td VALIGN=TOP>string</td>
     *	<td>The string name of the SSL cipher suite in use, if the
     *		request was made using SSL
     *  </td>
     * </tr>
     *
     * <tr>
     *	<td VALIGN=TOP>javax.net.ssl.peer_certificates</td>
     *	<td VALIGN=TOP>array of javax.security.cert.X509Certificate</td>
     *	<td>The chain of X.509 certificates which authenticates the client.
     *		This is only available when SSL is used with client
     *		authentication is used.
     *  </td>
     * </tr>
     *
     * <tr>
     *	<td VALIGN=TOP>javax.net.ssl.session</td>
     *	<td VALIGN=TOP>javax.net.ssl.SSLSession</td>
     *	<td>An SSL session object, if the request was made using SSL.</td>
     * </tr>
     *
     * </TABLE>
     *
     * <BR>
     * <P>The package (and hence attribute) names beginning with java.*,
     * and javax.* are reserved for use by Javasoft. Similarly, com.sun.*
     * is reserved for use by Sun Microsystems.
     *
     * @param name the name of the attribute whose value is required
     */
    public Object getAttribute(String name)
    {
        Object val = mRequest.getAttribute(name);
        if(val == null)
        {
            val = mAttributes.get(name);
        }
        return val;
    }

    /**
     * Returns a buffered reader for reading text in the request body.  This
     * translates character set encodings as appropriate.
     *
     * @see #getInputStream()
     *
     * @exception UnsupportedEncodingException if the character set encoding is
     * unsupported, so the text can't be correctly decoded.
     * @exception IllegalStateException if getInputStream has been * called on
     * this same request.
     * @exception IOException on other I/O related errors.
     */
    public BufferedReader getReader() throws IOException
    {
        return mRequest.getReader();
    }

    /**
     * Returns the character set encoding for the input of this request.
     */
    public String getCharacterEncoding()
    {
        return mRequest.getCharacterEncoding();
    }

    /**
     * Gets the array of cookies found in this request.
     *
     * @return the array of cookies found in this request
     */
    public Cookie[] getCookies()
    {
        return mRequest.getCookies();
    }

    /**
     * Gets the HTTP method (for example, GET, POST, PUT) with which this
     * request was made. Same as the CGI variable REQUEST_METHOD.
     *
     * @return the HTTP method with which this request was made
     */
    public String getMethod()
    {
        return mRequest.getMethod();
    }

    /**
     * Gets, from the first line of the HTTP request, the part of this
     * request's URI that is to the left of any query string.  For example,
     *
     * <blockquote>
     * <table>
     * <tr align=left><th>First line of HTTP request<th>
     * <th>Return from <code>getRequestURI</code>
     * <tr><td>POST /some/path.html HTTP/1.1<td><td>/some/path.html
     * <tr><td>GET http://foo.bar/a.html HTTP/1.0
     * <td><td>http://foo.bar/a.html
     * <tr><td>HEAD /xyz?a=b HTTP/1.1<td><td>/xyz
     * </table>
     * </blockquote>
     * 
     * <p>To reconstruct a URL with a URL scheme and host, use the method
     * javax.servlet.http.HttpUtils.getRequestURL, which returns a
     * StringBuffer.
     *
     * @return this request's URI
     * @see javax.servlet.http.HttpUtils#getRequestURL(HttpServletRequest)
     */
    public String getRequestURI() {
        return mVirtualPath;
    }

    /**
     * Gets the part of this request's URI that refers to the servlet being
     * invoked. Analogous to the CGI variable SCRIPT_NAME.
     *
     * @return the servlet being invoked, as contained in this request's URI
     */
    public String getServletPath()
    {
        return null;
    }

    /**
     * Gets any optional extra path information following the servlet path of
     * this request's URI, but immediately preceding its query string. Same as
     * the CGI variable PATH_INFO.
     *
     * @return the optional path information following the servlet path, but
     * before the query string, in this request's URI; null if this request's
     * URI contains no extra path information
     */
    public String getPathInfo()
    {
        return mVirtualPath;
    }

    /**
     * Gets any optional extra path information following the servlet path of
     * this request's URI, but immediately preceding its query string, and
     * translates it to a real path.  Similar to the CGI variable
     * PATH_TRANSLATED
     *
     * @return extra path information translated to a real path or null if no
     * extra path information is in the request's URI
     */
    public String getPathTranslated()
    {
        return mRealPath;
    }

    /**
     * Gets any query string that is part of the HTTP request URI.  Same as the
     * CGI variable QUERY_STRING.
     *
     * @return query string that is part of this request's URI, or null if it
     * contains no query string
     */
    public String getQueryString()
    {
        return mRequest.getQueryString();
    }

    /**
     * Gets the name of the user making this request.  The user name is set
     * with HTTP authentication.  Whether the user name will continue to be
     * sent with each subsequent communication is browser-dependent.  Same as
     * the CGI variable REMOTE_USER.
     *
     * @return the name of the user making this request, or null if not known.
     */
    public String getRemoteUser()
    {
        return mRequest.getRemoteUser();
    }

    /**
     * Gets the authentication scheme of this request.  Same as the CGI
     * variable AUTH_TYPE.
     *
     * @return this request's authentication scheme, or null if none.
     */
    public String getAuthType()
    {
        return mRequest.getAuthType();
    }

    /**
     * Gets the value of the requested header field of this request.  The case
     * of the header field name is ignored.
     * 
     * @param name the String containing the name of the requested header field
     * @return the value of the requested header field, or null if not known.
     */
    public String getHeader(String name)
    {
        return mRequest.getHeader(name);
    }

    /**
     * Gets the value of the specified integer header field of this request.
     * The case of the header field name is ignored.  If the header can't be
     * converted to an integer, the method throws a NumberFormatException.
     * 
     * @param name the String containing the name of the requested header field
     * @return the value of the requested header field, or -1 if not found.
     */
    public int getIntHeader(String name)
    {
        return mRequest.getIntHeader(name);
    }

    /**
     * Gets the value of the requested date header field of this request.  If
     * the header can't be converted to a date, the method throws an
     * IllegalArgumentException.  The case of the header field name is ignored.
     * 
     * @param name the String containing the name of the requested header field
     * @return the value the requested date header field, or -1 if not
     * found.
     */
    public long getDateHeader(String name)
    {
        return mRequest.getDateHeader(name);
    }

    /**
     * Gets the header names for this request.
     *
     * @return an enumeration of strings representing the header names for this
     * request. Some server implementations do not allow headers to be accessed
     * in this way, in which case this method will return null.
     */
    public Enumeration getHeaderNames()
    {
        return mRequest.getHeaderNames();
    }

    /**
     * Gets the current valid session associated with this request, if create
     * is false or, if necessary, creates a new session for the request, if
     * create is true.
     *
     * <p><b>Note</b>: to ensure the session is properly maintained, the
     * servlet developer must call this method (at least once) before any
     * output is written to the response.
     *
     * <p>Additionally, application-writers need to be aware that newly created
     * sessions (that is, sessions for which <code>HttpSession.isNew</code>
     * returns true) do not have any application-specific state.
     *
     * @return the session associated with this request or null if create was
     * false and no valid session is associated with this request.
     */
    public HttpSession getSession(boolean create)
    {
        return mRequest.getSession(create);
    }
   
    /**
     * Gets the session id specified with this request.  This may differ from
     * the actual session id.  For example, if the request specified an id for
     * an invalid session, then this will get a new session with a new id.
     *
     * @return the session id specified by this request, or null if the request
     * did not specify a session id
     * 
     * @see #isRequestedSessionIdValid
     */
    public String getRequestedSessionId()
    {
        return mRequest.getRequestedSessionId();
    }

    /**
     * Checks whether this request is associated with a session that is valid
     * in the current session context.  If it is not valid, the requested
     * session will never be returned from the <code>getSession</code> method.
     * 
     * @return true if this request is assocated with a session that is valid
     * in the current session context.
     *
     * @see #getRequestedSessionId
     * @see javax.servlet.http.HttpSessionContext
     * @see #getSession
     */
    public boolean isRequestedSessionIdValid()
    {
        return mRequest.isRequestedSessionIdValid();
    }

    /**
     * Checks whether the session id specified by this request came in
     * as a cookie.  (The requested session may not be one returned by
     * the <code>getSession</code> method.)
     * 
     * @return true if the session id specified by this request came in as a
     * cookie; false otherwise
     *
     * @see #getSession
     */
    public boolean isRequestedSessionIdFromCookie()
    {
        return mRequest.isRequestedSessionIdFromCookie();
    }

    /**
     * Checks whether the session id specified by this request came in as part
     * of the URL.  (The requested session may not be the one returned by the
     * <code>getSession</code> method.)
     * 
     * @return true if the session id specified by the request for this session
     * came in as part of the URL; false otherwise
     *
     * @see #getSession
     */
    public boolean isRequestedSessionIdFromUrl()
    {
        return mRequest.isRequestedSessionIdFromUrl();
    }

    /**
    * Returns the character set that was used when the parameters of the
    * request were encoded.  Used when encoding parameters for inclusion in
    * a URL
    */
    public String getURLCharSet()
    {
        return mURLCharSet;
    }

    /**
    * set URL Character Set.  The URL character set is the character set that
    * was used to encode the parameters of the request URL.  This is used
    * to decode the parameters and encode parameters that are used when
    * creating new URLs
    *
    * @param charset name of the character set used to encode the request URL
    */
    public void setURLCharSet(String charset)
    {
        // mURLCharSet has a default value of
        // HttpProviderConstants.HTTP_DEFAULT_CHARSET
        if (charset != null)
            mURLCharSet = charset;
    }

    /**
     * Add a 'local parameter' to this request. The name and value of the
     * parameter will then be available, along with the {@link
     * javax.servlet.http.HttpServletRequest} parameters, from calls to the
     * {@link #getParameter(String)}, {@link #getParameterValues(String)} and
     * {@link #getParameterNames()} methods.
     */
    public void addLocalParameter(String name, String value)
    {
        Vector v = (Vector)mParameters.get(name);
        if (v == null)
        {
            v = new Vector();
            mParameters.put(name, v);
        }
        v.addElement(value);
    }

     // adds a vector of decoded parameters to the local parameter set.
     //
     // This method is called from getParameter and getParameterValues
     // if those methods needed to decode a set of parameters.
    private void addLocalParameters(String name, Vector values)
    {
        Vector v = (Vector)mParameters.get(name);
        if (v == null)
        {
            mParameters.put(name, values);
            return;
        }

        // Parameters with this name already exist so add the new set
        // to the existing vector.
        Enumeration enum = values.elements();
        String value;
        while (enum.hasMoreElements())
        {
            value = (String)enum.nextElement();
            v.addElement(value);
        }
    }

   // Adds a set of parameters to the 'local parameter store' for this request
    private void addLocalParameters(Hashtable parameters)
    {
        Enumeration keys = parameters.keys();
        while(keys.hasMoreElements())
        {
            String key = (String)keys.nextElement();
            Enumeration values = ((Vector)parameters.get(key)).elements();
            while(values.hasMoreElements())
            {
                addLocalParameter(key, (String)values.nextElement());
            }
        }
    }

    private void addRawParameter(String name, String value)
    {
        Vector v = (Vector)mRawParameters.get(name);
        if (v == null)
        {
            v = new Vector();
            mRawParameters.put(name, v);
        }
        v.addElement(value);
    }

    // Populate the local parameter table with the decoded names and values
    // contained in this request (if any)
    private void processRequestParameters()
    {
        // First, get the 'query string' which holds the encoded parameter names
        // and values. In a 'GET' request, these are appended to the URL; in a
        // 'POST' request, these are contained in the request body.
        String queryString = null;
        if(mRequest.getMethod().equals(HttpProviderConstants.HTTP_GET_METHOD))
        {
           queryString = getQueryString();
        }
        else if(mRequest.getMethod().equals(HttpProviderConstants.HTTP_POST_METHOD) &&
                mRequest.getContentType().equals(HttpProviderConstants.HTTP_FORM_DATA_CONTENT_TYPE))
        {
          SimpleStringBuffer buff = new SimpleStringBuffer();
          try
          {
              // Not safe to trust HttpServletRequest.getReader()
              InputStream iStream = mRequest.getInputStream();
              BufferedInputStream bis = new BufferedInputStream(iStream);
              Reader r = new InputStreamReader(bis, "us-ascii");
              char[] readBuffer = new char[READ_BUFFER_SIZE];
              int bytesRead;
              // A bug in Servlet 2.0 means it is only safe to read
              // READ_BUFFER_SIZE-1 characters
              while((bytesRead = r.read(readBuffer, 0, READ_BUFFER_SIZE-1)) != -1)
              {
                  buff.append(readBuffer, 0, bytesRead);
              }
          }
          catch(IOException ioe)
          {
          }
          queryString = buff.toString();
        }
        // If we don't have a query string, we are done
        if(queryString == null)
        {
            return;
        }

        // Decide the character encoding with which 'escaped' bytes in the
        // query string have been encoded. If an encoding is specified in a
        // portal-specific "x-oracle-portal-content-charset" header, then use
        // it. Otherwise, default to "iso8859-1", which should result in
        // decoding behaviour similar to java.net.URLDecoder.
        String urlCharSet = mRequest.getHeader(HttpProviderConstants.HTTP_PORTAL_CONTENT_CHARSET);
        setURLCharSet(urlCharSet);

        // Create a tokenizer that uses '=' and '&' as delimiters, and returns
        // these as well as the delimited strings
        StringTokenizer st = new StringTokenizer(queryString, "=&", true);

        // Flag controls whether we are expecting next non-delimeter String to
        // be a name or a value
        boolean expectName = true;
        // Cache the last name here
        String paramName = null;
        String token = null;
        while(st.hasMoreTokens())
        {
            token = st.nextToken();

            //handle the two state transitions in which we
            //have to extract a (perhaps empty) name or value
            if(expectName && !token.equals("&"))
            {
                if(token.equals("="))
                {
                    paramName = "";
                }
                else
                {
                    paramName = token;
                }
                expectName = false;
            }
            else if(!expectName && !token.equals("="))
            {
                if(token.equals("&"))
                {
                    addQueryParameter(paramName, "");
                }
                else
                {
                    addQueryParameter(paramName, token);
                }
                expectName = true;
            }
         }
         if (!expectName)
         {
            // exited loop expecting a value. This will happen if the last param
            // did not have a value. Substitute "" for the value
            addQueryParameter(paramName, "");
         }
    }

    /**
     * Decodes parameter names and adds the raw parameter value to the
     * parameter store
     */
     private void addQueryParameter(String name, String value)
    {
        String decodedName = null;
        try
        {
            // decode the parameter name to make the implementation of
            // getParameter/getParameterValues/getParameterNames much
            // simpler. Very few parameter names will be encoded so the
            // cost of doing this up-front is less than the additional logic
            // required to handle it later
            decodedName = MultiByteDecoder.decode(name, getURLCharSet());
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
            throw new RuntimeException("UnsupportedEncodingException occurred " +
                                       "in addQueryParameter()");
        }
        addRawParameter(decodedName, value);
    }

    /**
     * Uses multi-byte URL decoder to decode parameter values associated
     * with the specified parameter name.
     *
     * Parameter name is decoded when the parameters are initially read so
     * it does not need to be decoded here
     */
    private Vector decodeParameter(String name)
    {
        Vector encodedValues = (Vector)mRawParameters.get(name);
        if (encodedValues == null)
        {
            return null;
        }

        Enumeration enum = encodedValues.elements();
        String encodedValue;
        String decodedValue;
        Vector decodedValues = new Vector(5);
        try
        {
            while (enum.hasMoreElements())
            {
                encodedValue = (String)enum.nextElement();
                decodedValue = MultiByteDecoder.decode(encodedValue,
                                                       getURLCharSet());
                decodedValues.addElement(decodedValue);
            }
        }
        catch (UnsupportedEncodingException e)
        {
            // This exception should never occur because the URLCharSet is
            // used in the MultiByteDecoder when the parameter names are
            // decoded during processRequestParameters()
            // Exception cannot be thrown because this method is called from
            // within getParameter() which is not declared to throw an exception
            e.printStackTrace();
            throw new RuntimeException("UnsupportedEncodingException thrown in" +
                                       "decodeParameters()");
        }
        addLocalParameters(name, decodedValues);
        mRawParameters.remove(name);
        return decodedValues;
    }

    // Copy over the render request's attributes into this request
    private void addRenderRequestAttributes(PortletRenderRequest pr)
    {
        Enumeration keys = pr.getAttributeNames();
        while(keys.hasMoreElements())
        {
            String key = (String)keys.nextElement();
            mAttributes.put(key, pr.getAttribute(key));
        }
    }

}
