/*  Copyright (c) 2002, Oracle Corporation. All Rights Reserved. */

package oracle.portal.provider.v2.webservice;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Enumeration;
import java.util.Iterator;
import javax.servlet.http.HttpServletRequest;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import oracle.webdb.utils.HTTPUtils;
import oracle.portal.utils.v2.NameValuePair;
import oracle.portal.provider.v2.ProviderUser;
import oracle.portal.provider.v2.ProviderSession;
import oracle.portal.provider.v2.PortletInstance;
import oracle.portal.provider.v2.PortletException;
import oracle.portal.provider.v2.personalize.NameValuePersonalizationObject;
import oracle.portal.provider.v2.security.AccessControlException;
import oracle.portal.provider.v2.render.PortletRenderRequest;
import oracle.portal.provider.v2.render.PortletRendererUtil;
import oracle.portal.provider.v2.http.HttpCommonConstants;

/**
 * A <code>WebServiceRenderRequest</code> 'wraps' a {@link
 * PortletRenderRequest} in order to expose properties and derived values that
 * are relevant to a {@link WebServiceRenderer}. A
 * <code>WebServiceRenderRequest</code> is also capable of 'serializing' itself
 * to XML.
 */
public class WebServiceRenderRequest
{
    PortletRenderRequest mPortletRenderRequest;
    private String mRepositoryPath;
    private String mPageURL;
    private String mFormActionLink;
    private String mRequestPath;
    private String mParameterPrefix;
    private NameValuePair[] mPageParams;
    private List mURLParams;
    private boolean mGotURLParams;
    private NameValuePersonalizationObject mEditData;
    private boolean mGotEditData;
    private static Map mRequestMappings = new HashMap(20);

    // Initialize static meta data about request property names
    static
    {
        mRequestMappings.put("user/name", new RequestParamMapping()
            {
                public String getValue(WebServiceRenderRequest wrr, String name)
                {
                    return wrr.getUser().getName();
                }
            });

        mRequestMappings.put("user/authLevel", new RequestParamMapping()
            {
                public String getValue(WebServiceRenderRequest wrr, String name)
                {
                    return String.valueOf(wrr.getUser().getAuthenticationLevel());
                }
            });

        mRequestMappings.put("locale/language", new RequestParamMapping()
            {
                public String getValue(WebServiceRenderRequest wrr, String name)
                {
                    return wrr.getLocale().getLanguage();
                }
            });

        mRequestMappings.put("locale/country", new RequestParamMapping()
            {
                public String getValue(WebServiceRenderRequest wrr, String name)
                {
                    return wrr.getLocale().getCountry();
                }
            });

        mRequestMappings.put("pageURL", new RequestParamMapping()
            {
                public String getValue(WebServiceRenderRequest wrr, String name)
                {
                    return wrr.getPageURL();
                }
            });

        mRequestMappings.put("formActionLink", new RequestParamMapping()
            {
                public String getValue(WebServiceRenderRequest wrr, String name)
                {
                    return wrr.getFormActionLink();
                }
            });

        mRequestMappings.put("requestPath", new RequestParamMapping()
            {
                public String getValue(WebServiceRenderRequest wrr, String name)
                {
                    return wrr.getRequestPath();
                }
            });

        mRequestMappings.put("parameterPrefix", new RequestParamMapping()
            {
                public String getValue(WebServiceRenderRequest wrr, String name)
                {
                    return wrr.getParameterPrefix();
                }
            });
    }

    public WebServiceRenderRequest(PortletRenderRequest pr)
    {
        mPortletRenderRequest = pr;
    }

    /**
     * Gets the portlet instance to be rendered.
     * @return the portlet instance to be rendered.
     */
    public PortletInstance getPortletInstance()
    {
        return mPortletRenderRequest.getPortletInstance();
    }

    /**
     * Gets the default storage path used for meta data associated with this
     * provider, if any. Usually, this is the filesystem path to the directory
     * containing provider.xml.
     *
     * @return the default storage path used for meta data associated with this
     * provider
     */
    public String getRepositoryPath()
    {
        if (mRepositoryPath == null)
        {
            mRepositoryPath = mPortletRenderRequest.getProviderInstance().getProviderContext().getRepositoryPath();
        }
        return mRepositoryPath;
    }

    /**
     * Gets an <code>Enumeration</code> over the parameter names in the query
     * string of the request to the provider. Note that parameter names are
     * automatically unqualified, i.e. the portlet instance specific prefix
     * returned by {@link #getParameterPrefix} is automatically removed.
     * @return an <code>Enumeration</code> over the parameter names in the query
     * string of the request to the provider.
     */
    public Enumeration getURLParameterNames()
    {
        if (!mGotURLParams)
        {
            if (mPortletRenderRequest.getPortletDefinition().getPassAllUrlParams())
            {
                mURLParams = new ArrayList();
                Enumeration e = mPortletRenderRequest.getParameterNames();
                String paramPrefix = getParameterPrefix();
                int prefixLen = paramPrefix.length();
                while (e.hasMoreElements())
                {
                    String name = (String)e.nextElement();
                    if (name.startsWith(paramPrefix))
                    {
                        name = name.substring(prefixLen);
                    }
                    mURLParams.add(name);
                }
            }
            mGotURLParams = true;
        }
        return mURLParams == null ?
            mPortletRenderRequest.getParameterNames() :
            Collections.enumeration(mURLParams);
                
    }

    /**
     * Gets the value of a specified parameter in the query string of the
     * request to the provider. Note that the given parameter name should be
     * unqualified, i.e. should not begin with the the portlet instance
     * specific prefix returned by {@link #getParameterPrefix}.
     * @param name the name of a parameter in the query string of the request
     * to the provider
     * @return the value of the specified parameter
     */
    public String getURLParameter(String name)
    {
        String value =  mPortletRenderRequest.getQualifiedParameter(name);
        // Make sure we see public parameters on 3.0.9 portals
        return value == null ? mPortletRenderRequest.getParameter(name) : value;
    }

    /**
     * Gets the value of a property of this <code>WebServiceRenderRequest</code> by name.
     * @param name the name of a property of this <code>WebServiceRenderRequest</code>. Allowable names are:
     *
     * <ul>
     *
     * <li><code>"user/name"</code>: The result of calling {@link ProviderUser#getName getUser().getName()}.</li>
     *
     * <li><code>"user/authLevel"</code>: The result of calling {@link
     * ProviderUser#getAuthenticationLevel
     * getUser().getAuthenticationLevel()}.</li>
     *
     * <li><code>"locale/language"</code>: The result of calling <code>getLocale().getLanguage()</code>.</li>
     *
     * <li><code>"locale/country"</code>: The result of calling <code>getLocale().getCountry()</code>.</li>
     *
     * <li><code>"pageURL"</code>: The result of calling {@link #getPageURL}.</li>
     *
     * <li><code>"formActionLink"</code>: The result of calling {@link #getFormActionLink}.</li>
     *
     * <li><code>"requestPath"</code>: The result of calling {@link #getRequestPath}.</li>
     *
     * <li><code>"parameterPrefix"</code>: The result of calling {@link #getParameterPrefix}.</li>
     *
     * </ul>
     *
     * @return the value of the specified property
     */
    public String getRequestParameter(String name)
    {
        return ((RequestParamMapping)mRequestMappings.get(name)).getValue(this, name);
    }

    /**
     * Gets the user on whose behalf the portlet is to be renderered.
     * @return the user on whose behalf the portlet is to be renderered
     */
    public ProviderUser getUser()
    {
        return mPortletRenderRequest.getUser();
    }

    /**
     * Gets the object representing the portal user's locale.
     * @return object representing the portal user's locale
     */
    public Locale getLocale()
    {
        return mPortletRenderRequest.getLocale();
    }

    /**
     * Gets the URL of the page that invoked this portlet. Note: Query string
     * is included if applicable.
     *
     * @return the URL of the page that invoked this portlet.
     */
    public String getPageURL()
    {
        if (mPageURL == null)
        {
            mPageURL = mPortletRenderRequest.getRenderContext().getPageURL();
        }
        return mPageURL;
    }

    /**
     * Gets the page URL without the query string, in a form that is suitable
     * for the action link of an HTML Form.
     *
     * @return the page URL without the query string
     */
    public String getFormActionLink()
    {
        if (mFormActionLink == null)
        {
            mFormActionLink = PortletRendererUtil.htmlFormActionLink(mPortletRenderRequest,
                                                                     PortletRendererUtil.PAGE_LINK);
        }
        return mFormActionLink;
    }

    /**
     *  Gets the path of the URL that invoked this portlet (without the
     *  filename and query string). Portlets use this to (re)write
     *  <code>HREF</code>s in their response, as these references must be
     *  absolute paths since the client browser considers the current target
     *  the portal.
     */
    public String getRequestPath()
    {
        if (mRequestPath == null)
        {
            HttpServletRequest servletReq = 
                (HttpServletRequest)mPortletRenderRequest.getAttribute(HttpCommonConstants.SERVLET_REQUEST);
            if (servletReq != null)
            {
                mRequestPath = servletReq.getScheme() + "://" + 
                    servletReq.getServerName()+ ":" +
                    servletReq.getServerPort() + 
                    servletReq.getContextPath() + "/";
            }
            else
            {
                mRequestPath = "";
            }
        }
        return mRequestPath;
    }

    /**
     * Gets the portlet instance specific prefix that should be used in front
     * of parameter names to make them private to the portlet instance.
     * @return the portlet instance specific prefix that should be used in
     * front of parameter names to make them private to the portlet instance.
     */
    public String getParameterPrefix()
    {
        if (mParameterPrefix == null)
        {
            mParameterPrefix = PortletRendererUtil.portletParameter(mPortletRenderRequest, "");
        }
        return mParameterPrefix;
    }

    /**
     * Gets the decoded names and values of the parameters in the query string
     * of the page URL. These parameter names and values represent the current
     * page 'context', which should be preserved by generated HTML forms by
     * including appropriate hidden fields.
     * @return the decoded names and values of the parameters in the query
     * string of the page URL
     */
    public NameValuePair[] getPageParams()
        throws PortletException
    {
        if (mPageParams == null)
        {
            List params = new ArrayList();
            String pageURL = getPageURL();
            String urlCharSet = mPortletRenderRequest.getURLCharSet();
            int lastIndex, nextIndex;
            lastIndex = pageURL.indexOf('?') + 1;
            String name, value;
            // efficient state machine for segmenting page URL, and handling
            // potentially 'empty' parameters
            // TODO: Get this into PortletRenderRequest.emitHiddenFields()
            while (lastIndex > 0)
            {
                nextIndex = pageURL.indexOf('=',lastIndex);
                // If the '=' is missing, assume we have a name with no value
                if (nextIndex == -1)
                {
                    nextIndex = pageURL.indexOf('&',lastIndex);
                    if (nextIndex == -1)
                    {
                        name = pageURL.substring(lastIndex);
                    }
                    else
                    {
                        name = pageURL.substring(lastIndex, nextIndex);
                    }
                    value = "";
                }
                else
                {
                    name = pageURL.substring(lastIndex, nextIndex);
                    lastIndex = nextIndex+1;
                    nextIndex = pageURL.indexOf('&',lastIndex);
                    if (nextIndex == -1)
                    {
                        value = pageURL.substring(lastIndex);
                    }
                    else
                    {
                        value = pageURL.substring(lastIndex, nextIndex);
                    }
                }
                lastIndex = nextIndex+1;
                try
                {
                    params.add(new NameValuePair(HTTPUtils.decode(name, urlCharSet),
                                                 HTTPUtils.decode(value, urlCharSet)));
                }
                catch (UnsupportedEncodingException uee)
                {
                    throw new PortletException(uee);
                }
            }
            mPageParams = new NameValuePair[params.size()];
            params.toArray(mPageParams);
        }
        return mPageParams;
    }

    /**
     * Retrieves a <code>NameValuePersonalizationObject</code> containing the
     * customizable data for the request user and portlet instance.
     * @return a <code>NameValuePersonalizationObject</code> containing the
     * customizable data for the request user and portlet instance, or
     * <code>null</code> if the portlet does not support customization, or the
     * dataclass is not {@link NameValuePersonalizationObject}.
     */
    public NameValuePersonalizationObject getEditData()
        throws PortletException
    {
        if (!mGotEditData)
        {
            Object data;
            try
            {
                data = PortletRendererUtil.getEditData(mPortletRenderRequest);
            }
            catch (IOException ioe)
            {
                throw new PortletException(ioe);
            }
            catch (AccessControlException ace)
            {
                throw new PortletException(ace);
            }
            if (data != null && data instanceof NameValuePersonalizationObject)
            {
                mEditData = (NameValuePersonalizationObject)data;
            }
            mGotEditData = true;
        }
        return mEditData;
    }

    /**
     * Gets the <code>ProviderSession</code> for the requesting user, if one
     * exists.
     * @return the <code>ProviderSession</code> for the requesting user, if one
     * exists, or <code>null</code> otherwise.
     */
    public ProviderSession getSession()
    {
        return mPortletRenderRequest.getSession();
    }

    /**
     * 'Serializes' this object to XML.
     * @param parent node in an XML document under which an XML representation
     * of this object should be added (inside an element called
     * <code>request</code>).
     */
    public void addToXml(Node parent)
        throws PortletException
    {
        Document doc;
        if (parent instanceof Document)
        {
            doc = (Document)parent;
        }
        else
        {
            doc = parent.getOwnerDocument();
        }
        Element requestEl = doc.createElement("request");
        parent.appendChild(requestEl);
        Element attribName;
        Text attribValue;

        // First use the request mappings table to map specific properties of
        // this request to elements
        Iterator r = mRequestMappings.keySet().iterator();
        Map elementMap = new HashMap(5);

        while (r.hasNext())
        {
            String paramPath = (String)r.next();

            // Where the element name is preceeded by a path, make sure the
            // element is 'parented' according to its path
            int sepIndex = paramPath.indexOf('/'), lastIndex = 0;
            Element pathParent = requestEl;
            String elementPath, elementName;
            while (sepIndex != -1)
            {
                elementPath = paramPath.substring(0, sepIndex);

                Element newEl = (Element)elementMap.get(elementPath);
                if (newEl == null)
                {
                    elementName = paramPath.substring(lastIndex, sepIndex);
                    newEl = doc.createElement(elementName);
                    pathParent.appendChild(newEl);
                    elementMap.put(elementPath, newEl);
                }
                pathParent = newEl;
                lastIndex = sepIndex+1;
                sepIndex = paramPath.indexOf('/', lastIndex);
            }

            // Now create the actual 'leaf' element
            if (lastIndex > 0)
            {
                elementName = paramPath.substring(lastIndex);
            }
            else
            {
                elementName = paramPath;
            }
            attribName = doc.createElement(elementName);
            pathParent.appendChild(attribName);
            attribValue = doc.createTextNode(getRequestParameter(paramPath));
            attribName.appendChild(attribValue);
        }

        Element paramsEl = doc.createElement("pageParams");
        parent.appendChild(paramsEl);
        NameValuePair[] pageParams = getPageParams();
        for (int i=0; i<pageParams.length; i++)
        {
            Element paramEl = doc.createElement("param");
            paramsEl.appendChild(paramEl);
            attribName = doc.createElement("name");
            paramEl.appendChild(attribName);
            attribValue = doc.createTextNode(pageParams[i].getName());
            attribName.appendChild(attribValue);
            attribName = doc.createElement("value");
            paramEl.appendChild(attribName);
            attribValue = doc.createTextNode(pageParams[i].getValue());
            attribName.appendChild(attribValue);
        }

        Element privparamsEl = doc.createElement("urlParams");
        parent.appendChild(privparamsEl);
        Enumeration e = getURLParameterNames();
        while (e.hasMoreElements())
        {
            String param = (String)e.nextElement();
            Element paramEl = doc.createElement("param");
            privparamsEl.appendChild(paramEl);
            attribName = doc.createElement("name");
            paramEl.appendChild(attribName);
            attribValue = doc.createTextNode(param);
            attribName.appendChild(attribValue);
            attribName = doc.createElement("value");
            paramEl.appendChild(attribName);
            attribValue = doc.createTextNode(getURLParameter(param));
            attribName.appendChild(attribValue);
        }

        NameValuePersonalizationObject editData = getEditData();
        if (editData != null)
        {
            Element editDataEl = doc.createElement("editData");
            parent.appendChild(editDataEl);
            Iterator i = editData.getNames();
            while (i.hasNext())
            {
                String name = (String)i.next();
                Element itemEl = doc.createElement("item");
                editDataEl.appendChild(itemEl);
                attribName = doc.createElement("name");
                itemEl.appendChild(attribName);
                attribValue = doc.createTextNode(name);
                attribName.appendChild(attribValue);
                attribName = doc.createElement("value");
                itemEl.appendChild(attribName);
                try
                {
                    attribValue = doc.createTextNode(editData.getString(name));
                    attribName.appendChild(attribValue);
                }
                // If the datum is not a string, just leave it out
                catch (ClassCastException cce)
                {
                }
            }
        }

        ProviderSession session = getSession();
        if (session != null)
        {
            Element sessionEl = doc.createElement("session");
            parent.appendChild(sessionEl);
            e = session.getAttributeNames();
            while (e.hasMoreElements())
            {
                String attrib = (String)e.nextElement();
                Object value = session.getAttribute(attrib);
                // Avoid adding the ProviderSession's own reference to itself
                if (value instanceof ProviderSession)
                {
                    continue;
                }
                Element attribEl = doc.createElement("attribute");
                sessionEl.appendChild(attribEl);
                attribName = doc.createElement("name");
                attribEl.appendChild(attribName);
                attribValue = doc.createTextNode(attrib);
                attribName.appendChild(attribValue);
                attribName = doc.createElement("value");
                attribEl.appendChild(attribName);
                attribValue = doc.createTextNode(value.toString());
                attribName.appendChild(attribValue);
            }
        }
    }

    static private abstract class RequestParamMapping
    {
        public abstract String getValue(WebServiceRenderRequest wrr, String name);
    }

}
