Package org.apache.myfaces.portlet.faces.context

Source Code of org.apache.myfaces.portlet.faces.context.PortletExternalContextImpl

/* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/

package org.apache.myfaces.portlet.faces.context;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;

import java.net.MalformedURLException;
import java.net.URL;

import java.security.Principal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import javax.faces.render.ResponseStateManager;

import javax.portlet.ActionResponse;
import javax.portlet.BaseURL;
import javax.portlet.ClientDataRequest;
import javax.portlet.MimeResponse;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletResponse;
import javax.portlet.PortletURL;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceURL;
import javax.portlet.StateAwareResponse;
import javax.portlet.WindowState;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.BridgeDefaultViewNotSpecifiedException;
import javax.portlet.faces.BridgeInvalidViewPathException;

import org.apache.myfaces.portlet.faces.bridge.BridgeImpl;
import org.apache.myfaces.portlet.faces.util.FacesVersionUtil;
import org.apache.myfaces.portlet.faces.util.QueryString;
import org.apache.myfaces.portlet.faces.util.URLUtils;
import org.apache.myfaces.portlet.faces.util.map.EnumerationIterator;
import org.apache.myfaces.portlet.faces.util.map.PortletApplicationMap;
import org.apache.myfaces.portlet.faces.util.map.PortletInitParameterMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestHeaderMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestHeaderValuesMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestHeaders;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestParameterMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestParameterValuesMap;
import org.apache.myfaces.portlet.faces.util.map.PortletSessionMap;

/**
* This implementation of {@link ExternalContext} is specific to the portlet implementation.
*
* Methods of interests are: - encodeActionURL - redirect
*/
public class PortletExternalContextImpl
  extends ExternalContext
{

  public static final String FACES_MAPPING_ATTRIBUTE =
    "org.apache.myfaces.portlet.faces.context.facesMapping";
 
  // Note: be careful -- as this attribute is prefixed to a value containg '.' it
  // wouldn't be exlcuded using normal logic -- so instead BridgeImpl specially
  // exlcudes/treats this package. -- i.e. all attrbiutes beginning with
  // "org.apache.myfaces.portlet.faces.context." are excluded -- so beware if
  // you try and add an attribute you don't want exlcuded within this package.
  private static final String ENCODED_ACTION_URL_ATTRIBUTE_PREFIX =
    "org.apache.myfaces.portlet.faces.context.";

  public static final String RENDER_POLICY_ATTRIBUTE =
    Bridge.BRIDGE_PACKAGE_PREFIX + "." + Bridge.RENDER_POLICY;

  // Render parameter to store the viewId
  public static final String JSF_TARGET_VIEWID_RENDER_PARAMETER = "__jpfbJSFTARGET";
  public static final String NO_SCOPE = "org.apache.myfaces.portlet.faces.noScope";
 
  public static final String SERVLET_INCLUDED_PATHINFO_ATTRIBUTE = "javax.servlet.include.path_info";
  public static final String SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
 
  // Hack to work (best) with existing releases of Liferay
  private static final String LIFERAY_NAMESPACE_PREFIX_HACK = "lfr_";


  private PortletContext mPortletContext;
  private PortletRequest mPortletRequest;
  private PortletResponse mPortletResponse;
  private Object mTempNonPortletResponse;
 
  private String mPortletName;
  private String mDefaultRenderKitId;

  // External context maps
  private Map<String, Object> mApplicationMap = null;
  private Map<String, Object> mSessionMap = null;
  private Map<String, Object> mRequestMap = null;
  private Map<String, String> mRequestParameterMap = null;
  private Map<String, String[]> mRequestParameterValuesMap = null;
  private Map<String, String> mRequestHeaderMap = null;
  private Map<String, String[]> mRequestHeaderValuesMap = null;
  private Map<String, String> mInitParameterMap = null;

  // maps for internal parameters (eg, those specified in query string of
  // any defaultViewId) --
  private Map<String, String> mTempExtraRequestParameterMap = null;
  private Map<String, String[]> mTempExtraRequestParameterValuesMap = null;

  private PortletRequestHeaders mPortletRequestHeaders = null;

  // Requested Faces view
  private String mViewId = null;

  // Reverse engineered serlvet paths from mappings
  private List<String> mFacesMappings = null;
  private String mServletPath = null;
  private String mPathInfo = null;
  private String mIncludedServletPath = null;
  private String mIncludedPathInfo = null;
 
  private boolean mUseIncludeAttributeServletDependencyWorkaround;
 
  @SuppressWarnings("unchecked")
  public PortletExternalContextImpl(PortletContext portletContext, PortletRequest portletRequest,
                                    PortletResponse portletResponse)
    throws FacesException
  {
    mPortletContext = portletContext;
    mPortletRequest  = portletRequest;
    mPortletResponse = portletResponse;
   
    mPortletName = (String) mPortletRequest.getAttribute(BridgeImpl.PORTLET_NAME_ATTRIBUTE);
    mDefaultRenderKitId = (String) mPortletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + mPortletName +
                                              "." + Bridge.DEFAULT_RENDERKIT_ID);

    mFacesMappings = (List<String>) mPortletRequest.getAttribute(FACES_MAPPING_ATTRIBUTE);
   
    setFacesVersionDependencyFlags();
   
    // Local portals commonly use the servlet dispatcher to execute the portlet container
    // A side effect of this is the javax.servlet.include path attributes are set
    // Unfortunately, Faces resolves viewId targets by first looking at these attributes
    // prior to consulting the externalContext (request info).  To avoid Faces
    // from using these bad values -- clear them by caching them (we restore on release)
    mIncludedPathInfo = (String) mPortletRequest.getAttribute(SERVLET_INCLUDED_PATHINFO_ATTRIBUTE);
    mIncludedServletPath = (String) mPortletRequest.getAttribute(SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE);
    mPortletRequest.removeAttribute(SERVLET_INCLUDED_PATHINFO_ATTRIBUTE);
    mPortletRequest.removeAttribute(SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE);
   
    // Because determining the view accesses request parameters -- delay until its demanded
    // so clients can still set request character encoding.
  }
 
  /**
   * Sometimes the bridge has to workaround issues related to specific versions of a Faces
   * implementation that can't be done in a generic/unobtrusive way.  Here we try to determine
   * which workarounds should be enabled.  Currently there is only one: Faces RI (Mojarra) versions
   * before 1.2_13 contained some servlet dependent code that gets hit during view resolution unless
   * the bridge has set the ServletPath include attribute.  Unfortunately, Liferay (5.2) portlet container
   * also writes/uses this attribute. 
   */
  private void setFacesVersionDependencyFlags()
  {
    mUseIncludeAttributeServletDependencyWorkaround = true;
    // First check to see if there is a web.xml init parameter setting
    String disable = mPortletContext.getInitParameter("org.apache.myfaces.portlet.bridge.disableMojarraViewResolutionWorkaround");
    if (disable != null)
    {
      mUseIncludeAttributeServletDependencyWorkaround = !Boolean.valueOf(disable).booleanValue();
    }
    else
    {
    switch (FacesVersionUtil.getFacesType())
      {
        case MOJARRA:
          if (FacesVersionUtil.getFacesImplPatchVersion() >= 13)
          {
            mUseIncludeAttributeServletDependencyWorkaround = false;
          }
          break;
        case MYFACES:
          mUseIncludeAttributeServletDependencyWorkaround = false;
          break;
        default:
          mUseIncludeAttributeServletDependencyWorkaround = true;
          break;
      }
    }
  }
 
  public void release()
  {

    // Restore the included path attributes if we unset them
    if (mIncludedPathInfo != null)
    {
      mPortletRequest.setAttribute(SERVLET_INCLUDED_PATHINFO_ATTRIBUTE, mIncludedPathInfo);
    }
    if (mIncludedServletPath != null)
    {
      mPortletRequest.setAttribute(SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE, mIncludedServletPath);
    }
                                                                                                          
    mPortletContext = null;
    mPortletRequest = null;
    mPortletResponse = null;

    mApplicationMap = null;
    mSessionMap = null;
    mRequestMap = null;
    mRequestParameterMap = null;
    mRequestParameterValuesMap = null;
    mRequestHeaderMap = null;
    mRequestHeaderValuesMap = null;
    mInitParameterMap = null;

    mViewId = null;
  }

  /**
   * This method is the gatekeeper for managing the viewId across action/render + subsequent
   * renders.
   *
   * For the render case, when rendering the actionURL, we call this method to write the viewId in
   * the interaction state when calling createActionURL() This allows us to get the viewId in action
   * request. eg, /adf-faces-demo/componentDemos.jspx?_VIEW_ID=/componentDemos.jspx
   *
   * For the action with redirect case, we call this method when the redirect() is called and we
   * encode the viewId in the navigational state so we can get the viewId in the subsequent render
   * request eg, /adf-faces-demo/componentDemos.jspx?_VIEW_ID=/componentDemos.jspx
   *
   * We do the same as above for the action with non-redirect case as well by calling the redirect()
   * method at the end of action lifecycle in ADFBridgePorttlet.process() by passing in an URL
   * created by ViewHandler.getActionURL()
   *
   * A special case to handle direct call from the goLink/goButton component in render request (bug
   * 5259313) eg, /components/goButton.jspx or http://www.oracle.com
   */
  @Override
  public String encodeActionURL(String url)
  {
    String viewId = null, path = null;
    QueryString queryStr = null;
    int queryStart = -1;
    boolean isPortletURL = false;
    boolean isPortletURLSelfReference = false;
    boolean isStrictXhtmlEncoded = isStrictXhtmlEncoded(url);
    Bridge.PortletPhase urlType = Bridge.PortletPhase.ACTION_PHASE;

    // First check to see if the special URI indicating we should encode
    // a Nonfaces target to just the current portlet (without an associated
    // path based resource).
    if (isPortletURL(url))
    {
      isPortletURL = true;
      //URL is of the form scheme:urlType?queryString
      // remove the scheme
      path = url.substring(url.indexOf(":")+ 1);
      queryStart = path.indexOf('?');

      if (queryStart != -1)
      {
        // Get the query string
        queryStr = new QueryString(path.substring(queryStart + 1), "UTF8");
        path = path.substring(0, queryStart);
      }
     
      if (path.equalsIgnoreCase("render"))
      {
        urlType = Bridge.PortletPhase.RENDER_PHASE;
      }
      else if (path.equalsIgnoreCase("action"))
      {
        urlType = Bridge.PortletPhase.ACTION_PHASE;
      }
      else if (path.equalsIgnoreCase("resource"))
      {
        urlType = Bridge.PortletPhase.RESOURCE_PHASE;
      }
      else
      {
        log("PortletExternalContextImpl.encodeActionURL:  malformed portlet url "
            + url);
        return url;
      }
     
      // We allow use of this syntax to reference another (or this) jsf page --
      // For example if one wants to create a redisplay link for this page
      // we recognize its a JSF page because it includes a QS parameter
      // that references either the viewId or viewPath
      String s = queryStr.getParameter(Bridge.FACES_VIEW_ID_PARAMETER);
      String s1 = queryStr.getParameter(Bridge.FACES_VIEW_PATH_PARAMETER);
      if (s != null && s.equals(Bridge.FACES_USE_CURRENT_VIEW_PARAMETER))
      {
        isPortletURLSelfReference = true;
        // by removing the parameter we will rely on retaining the current view info based on \
        // the current render params
        queryStr.removeParameter(Bridge.FACES_VIEW_ID_PARAMETER);
      }
      else if (s1 != null && s1.equals(Bridge.FACES_USE_CURRENT_VIEW_PARAMETER))
      {
        isPortletURLSelfReference = true;
        // by removing the parameter we will rely on retaining the current view info based on \
        // the current render params
        queryStr.removeParameter(Bridge.FACES_VIEW_PATH_PARAMETER);
      }
    }
    else if (url.startsWith("#") || isAbsoluteURL(url))
    {
      return url;
   
    else if (isDirectLink(url))
    {
      // its not an absolute URL (or would have been handled in previous if) convert it into one.
      return getAbsoluteUrlFromPath(url);
    }
    else
    {
      // Its a Path encoded URL
     
      // url might contain DirectLink=false parameter -- spec says remove if
      // it does.
      url = removeDirectLink(url);

      // Now determine the target viewId

      // First: split URL into path and query string
      // Hold onto QueryString for later processing
      queryStart = url.indexOf('?');

      if (queryStart != -1)
      {
        // Get the query string
        queryStr = new QueryString(url.substring(queryStart + 1), "UTF8");
        path = url.substring(0, queryStart);
      }
      else
      {
        path = url;
        // construct an empty queryString to hold the viewId
        queryStr = new QueryString("UTF8");
      }
   
      // Convert relative path to context path
      if (isRelativePath(path))
      {
        path = getPathFromRelativePath(path);
      }
   
      // Now se if this is a Faces URL
      viewId = getViewIdFromPath(path);

      if (viewId != null)
      {
        encodeFacesActionTarget(queryStr, viewId);
      }
      else
      {
        // URL points at non-Faces action
        // Non-JSF actions are renderURLs as we merely dispatch to them
        urlType = Bridge.PortletPhase.RENDER_PHASE;
        encodeNonFacesActionTarget(queryStr, path);
      }
    }
   
    if (getPortletPhase() == Bridge.PortletPhase.RENDER_PHASE ||
        getPortletPhase() == Bridge.PortletPhase.RESOURCE_PHASE)
    { // render - write
      // the viewId into
      // the response
      // (interaction
      // state)
      MimeResponse mimeResponse = (MimeResponse) getResponse();
      PortletURL actionURL = null;
      ResourceURL resourceURL = null;
      BaseURL baseURL = null;
      // Non-JSF actions are renderURLs as we merely dispatch to them
      if (urlType == Bridge.PortletPhase.ACTION_PHASE)
      {
        baseURL = actionURL = mimeResponse.createActionURL();
      }
      else if (urlType == Bridge.PortletPhase.RESOURCE_PHASE)
      {
        baseURL = resourceURL = mimeResponse.createResourceURL();
      }
      else
      {
        baseURL = actionURL = mimeResponse.createRenderURL();
      }

      // Add parameters so they don't get lost
      Enumeration<String> list = queryStr.getParameterNames();
      while (list.hasMoreElements())
      {
        String param = list.nextElement().toString();
        if (actionURL != null && param.equals(Bridge.PORTLET_MODE_PARAMETER))
        {
          try
          {
            actionURL.setPortletMode(new PortletMode(queryStr.getParameter(param)));
          }
          catch (Exception e)
          {
            ; // do nothing -- just ignore
          }
        }
        else if (actionURL != null && param.equals(Bridge.PORTLET_WINDOWSTATE_PARAMETER))
        {
          try
          {
            actionURL.setWindowState(new WindowState(queryStr.getParameter(param)));
          }
          catch (Exception e)
          {
            ; // do nothing -- just ignore
          }
        }
        else if (param.equals(Bridge.PORTLET_SECURE_PARAMETER))
        {
          try
          {
            baseURL.setSecure(Boolean.getBoolean(queryStr.getParameter(param)));
          }
          catch (Exception e)
          {
            ; // do nothing -- just ignore
          }
        }
        else
        {
          baseURL.setParameter(param, queryStr.getParameter(param));
        }
      }
     
      // Carry forward render parameters if this is a portlet:url that references the current view
      if (isPortletURLSelfReference)
      {
        Map<String, String[]> addedActionParams = (Map<String, String[]>) getRequestMap().get(BridgeImpl.REQUEST_PARAMETERS);
       
        if (addedActionParams == null)
        {
          addedActionParams = Collections.EMPTY_MAP;
        }
        carryForwardRenderParameters(baseURL, mPortletRequest.getPrivateParameterMap(), queryStr, addedActionParams);
        carryForwardRenderParameters(baseURL, mPortletRequest.getPublicParameterMap(), queryStr, addedActionParams);
      }
     
      // JSF expects encodeActionURL to not perturb any XML encoding (or lack thereof) of the url.
      // I.e. the caller is responsible for either pre or post XML escaping the string sent/returned
      // by encodeActionURL.  As Portlet 2.0 provides specific apis for xml encoding/escaping the url (or not),
      // use the right form based on what we saw in the incoming url
      url = portletURLToString(baseURL, isStrictXhtmlEncoded);

    }
    else if (getPortletPhase() == Bridge.PortletPhase.ACTION_PHASE ||
             getPortletPhase() == Bridge.PortletPhase.EVENT_PHASE)
    { // action - write the viewId to navigational state
      StateAwareResponse stateResponse = (StateAwareResponse) getResponse();

      // set request params into navigational states
      Enumeration<String> list = queryStr.getParameterNames();
      while (list.hasMoreElements())
      {
        String param = list.nextElement();
        if (param.equals(Bridge.PORTLET_MODE_PARAMETER))
        {
          try
          {
            stateResponse.setPortletMode(new PortletMode(queryStr.getParameter(param)));
          }
          catch (Exception e)
          {
            //TODO: Ignoring is probably dangerous here as it means that we are
            //      EITHER using exceptions for flow control (which is extreemly
            //      inefficient) or we should log a message saying what the issue
            //      is.  According to the Javadocs an exception is thrown here if the
            //      portlet mode is not allowed or if sendRedirect has already been
            //      called.  In either case we should log an information type message
            //      here.
            ; // do nothing -- just ignore
          }
        }
        else if (param.equals(Bridge.PORTLET_WINDOWSTATE_PARAMETER))
        {
          try
          {
            stateResponse.setWindowState(new WindowState(queryStr.getParameter(param)));
          }
          catch (Exception e)
          {
            ; // do nothing -- just ignore
          }
        }
        else if (param.equals(Bridge.PORTLET_SECURE_PARAMETER))
        {
          ; // ignore -- do nothing as can't encode into an actionResponse
        }
        else
        {
          stateResponse.setRenderParameter(param, queryStr.getParameter(param));
        }
      }
    }
    // Because we want to support translating a redirect that occurs
    // during a render as an in place navigation AND we can't reverse
    // engineer the URL from the actionURL, we stash the queryStr on
    // a request attribute, keyed with the generated URL.  If this generated
    // url is passed to redirect() we can get the queryStr back and
    // process based on it.  Do here rather then in the render if statement
    // because redirect relies on this to handle non-encoded Faces URLs whether
    // in an action or a render.
    getRequestMap().put(ENCODED_ACTION_URL_ATTRIBUTE_PREFIX.concat(url), queryStr);
   
    return url;
  }
 
  private void carryForwardRenderParameters(BaseURL url, Map<String, String[]> m, QueryString queryStr, Map<String, String[]> addedActionParams)
  {
    Set<Map.Entry<String, String[]>> s = m.entrySet();
    Iterator<Map.Entry<String, String[]>> i = s.iterator();
    while (i.hasNext())
    {
      Map.Entry<String, String[]> entry = i.next();
      // only add if not in the current queryString and not one of the bridge added/carried forward ActionParams
      if (queryStr.getParameter(entry.getKey()) == null && addedActionParams.get(entry.getKey()) == null)
      {
        url.setParameter(entry.getKey(), entry.getValue());
      }
    }
  }

  @Override
  public void redirect(String url)
    throws IOException
  {
    Bridge.PortletPhase phase = getPortletPhase();
   
    switch (phase)
    {
    case ACTION_PHASE:
      redirectDuringAction(url);
      return;
    case EVENT_PHASE:
      redirectDuringEvent(url);
      return;
    case RENDER_PHASE:
      redirectDuringRender(url);
      return;
    case RESOURCE_PHASE:
    default:
      return; // as per spec if a resource request its a noop as we shouldn't get here.
    }
  }
 
  private void redirectDuringAction(String url)
    throws IOException
  {
    // Distinguish between redirects to other Faces views in this app
    // and everything else.
    // Redirects to a view are dealt (elsewhere) as navigations --
    // encode this information for later use by the bridge controller.
    // Other links are redirected.
   
    // First look to see if this is an already encoded
    QueryString params = (QueryString) getRequestMap().get(ENCODED_ACTION_URL_ATTRIBUTE_PREFIX.concat(url));
    if (params != null)
    {
      // Because we want to support translating a redirect that occurs
      // during a render as an in place navigation AND we can't reverse
      // engineer the URL from the actionURL, we stash the original URL on
      // a request attribute, keyed with the generated URL.  If this generated
      // url is passed to redirect() we can get the original url back and
      // process based on it.
     
      getRequestMap().put(BridgeImpl.REDIRECT_VIEWPARAMS, params);
      FacesContext.getCurrentInstance().responseComplete();
    }
    else if ((url.startsWith("#") || isExternalURL(url) || isDirectLink(url)))
    {
      ((ActionResponse) getResponse()).sendRedirect(url);
      FacesContext.getCurrentInstance().responseComplete();

    }
    else
    {
      // is it an unencoded Faces URL -- process it.
      // recurse on redirect after calling encodeActionURL which will
      // cause the attribute to be set.
      redirectDuringAction(encodeActionURL(url));
    }
   
     // else nothing to do
     // TODO:  is there an exception to throw here?
  }
 
  private void redirectDuringEvent(String url)
    throws IOException
  {
    redirectPortletView(url, false);
  }
 
  private void redirectDuringRender(String url)
    throws IOException
  {
    // As this doesn't go back to the consumer -- we need to reencode the public render parameters so they are there
    redirectPortletView(url, true);
  }
 
  private void encodeRenderRedirectPublicRenderParameters(QueryString queryStr)
  {
    // If there are no Public Render Parameters just return the inUrl
    Map<String, String[]> m = mPortletRequest.getPublicParameterMap();
    if (m == null || m.isEmpty())
    {
      return;
    }
   
    // Otherwise -- there are public render parameters that need to be encoded
    for (Map.Entry<String, String[]> entry:m.entrySet())
    {
      String key = entry.getKey();
     
      // Only add if not already reflected in the QS
      if (queryStr.getParameter(key) == null)
      {
        for (String param:entry.getValue())
        {
          queryStr.addParameter(key, param);
       
      }
    }
  }
 
  private void redirectPortletView(String url, boolean isRenderPhase)
    throws IOException
  {
    // Distinguish between redirects to other Faces views in this app
    // and everything else.
    // Redirects to a view are dealt (elsewhere) as navigations --
    // encode this information for later use by the bridge controller.
    // Other links are ignored.
   
    // First look to see if this is an already encoded
    QueryString params = (QueryString) getRequestMap().get(ENCODED_ACTION_URL_ATTRIBUTE_PREFIX.concat(url));
    if (params != null)
    {
      // Only can redirect to other Faces views in an render request
      if (isRenderPhase && params.getParameter(JSF_TARGET_VIEWID_RENDER_PARAMETER) == null)
      {
        throw new IllegalStateException("Can't redirect during render to a NonFaces target: " + url);
      }
      // Because we want to support translating a redirect that occurs
      // during a render as an in place navigation AND we can't reverse
      // engineer the URL from the actionURL, we stash the original URL on
      // a request attribute, keyed with the generated URL.  If this generated
      // url is passed to redirect() we can get the original url back and
      // process based on it.
     
      if (isRenderPhase)
      {
        encodeRenderRedirectPublicRenderParameters(params);
      }
   
      getRequestMap().put(BridgeImpl.REDIRECT_VIEWPARAMS, params);
      FacesContext.getCurrentInstance().responseComplete();
    }
    else if ((url.startsWith("#") || isExternalURL(url) || isDirectLink(url)))
    {
      // do nothing as per spec -- can't redirect the consumer from an event

    }
    else
    {
      // is it an unencoded Faces URL -- process it.
      // recurse on redirect after calling encodeActionURL which will
      // cause the attribute to be set.
      redirectPortletView(encodeActionURL(url), isRenderPhase);
    }
   
     // else nothing to do
     // TODO:  is there an exception to throw here?
  }


  @Override
  public String encodeResourceURL(String s)
  {
    if (isPortletURL(s))
    {
      return urlEncode(encodeResourceAsViewNavigationURL(s));
    }
    else if (isOpaqueURL(s))
    {
      // spec says return this unchanged
      return s;
    }
    else if (isExternalURL(s))
    {
      return urlEncode(encodeResourceURL(s, false));
    }
    else if (isViewLink(s))
    {
      return urlEncode(encodeResourceAsViewNavigationURL(s));
    }
    else if (!isInProtocolResourceLink(s))
    {
      return urlEncode(encodeResourceURL(s, true));
    }
    else
    {
      return urlEncode(encodePortletServedResourceURL(s));
    }
  }
 
  private String urlEncode(String s)
  {
    // replace all spaces in the url with %20 to avoid Faces from encoding with a +
    return s.replace(" ", "%20");
  }
 
  private String encodeResourceURL(String s, boolean targetInApp)
  {
    boolean isStrictXhtmlEncoded = isStrictXhtmlEncoded(s); // do here because replaceResourceQSMarkers changes
   
    if (targetInApp)
    {
      s = normalizeResourcePath(s);
    }
   
    // Check for backlink and viewlink markers -- if they exist replace
    s = replaceResourceQueryStringMarkers(s);

    // If we weren't already Xhtml encoded (used &amp;) then renderkit will likely
    // do it after us if it needs to do such encoding -- so undo any xml encoding the portlet URL might
    // have done to avoid double encoding -- Otherwise if the string passed in was encoded the renderkit
    // is unlikely to reencode after the call -- so don't undo any encoding.
    if (!isStrictXhtmlEncoded)
    {
   
      // Some portlet containers implementing wsrp choose to separate the
      // consumer rewrite string that represents this URL using &amp; as the
      // spec allows for either that or &.  If the container has chosen to
      // to do such -- undo it (use & instead) as faces renderkits/response writers
      // may reencode when writing XMl content.
      return mPortletResponse.encodeURL(s).replace("&amp;", "&");
    }
    else
    {
      return xhtmlEncode(mPortletResponse.encodeURL(s));
    }
  }
 
  private String normalizeResourcePath(String s)
  {
    if (!s.startsWith("/"))
    {
      // must be a relative path -- convert it to contextPath relative
      // construct our cwd (servletPath + pathInfo);
      String pi = null;
      String path = getRequestServletPath();
      if (path == null)
      {
        path = getRequestPathInfo();
      }
      else
      {
        pi = getRequestPathInfo();
      }

      if (pi != null)
      {
        path = path.concat(pi);
      }

      // remove target
      path = path.substring(0, path.lastIndexOf("/"));
      s = URLUtils.convertFromRelative(path, s);
    }
   
    // prepend the context path since portletResponse.encodeURL() requires a full path URI
    // Don't need to check return from getRequestContextPath because there must
    // always be a vlaue even if an empty string
    String ctxPath = getRequestContextPath();
    if (ctxPath.length() > 0 && !s.startsWith(ctxPath))
    {
      s = ctxPath + s;
    }
   
    return s;
  }
 
  private String encodeResourceAsViewNavigationURL(String s)
  {
    // Check for backlink and viewlink markers -- if they exist replace
    s = replaceResourceQueryStringMarkers(s);
   
    // encodeActionURL takes acre of &amp; encoding issues
    return encodeActionURL(s);
  }
 
 
  private String encodePortletServedResourceURL(String s)
  {
    return encodePortletResourceURL(normalizeResourcePath(s));
  }
 
  private String encodePortletResourceURL(String url)
  {
    boolean isStrictXhtmlEncoded = isStrictXhtmlEncoded(url);
    // If a Faces resource, we assume that clients have called
    // ViewHandler.getActionURL() before calling encodeResourceURL when
    // doing a partial page URL.  Hence all other framework queryString
    // params should already be encoded.
   
    // Though might prefer to defer this analysis until the request is
    // submitted this info is available within ExternalContext but not the
    // bridge code -- so we would have to reestablish the faces context to
    // do it then even if we subsequently don't need it because its not a
    // Faces resource.
   
    // Determine if there is a target viewId
    String viewId = null, path = null;
    QueryString queryStr = null;
    int queryStart = -1;

    // First: split URL into path and query string
    // Hold onto QueryString for later processing
    queryStart = url.indexOf('?');

    if (queryStart != -1)
    {
      // Get the query string
      queryStr = new QueryString(url.substring(queryStart + 1), "UTF8");
      path = url.substring(0, queryStart);
    }
    else
    {
      path = url;
      // construct an empty queryString to hold the viewId
      queryStr = new QueryString("UTF8");
    }
   
    // Now remove up through the ContextPath as we don't want it
    String ctxPath = getRequestContextPath();
    int i = path.indexOf(ctxPath);
    if (i != -1)
    {
      path = path.substring(i + ctxPath.length());
    }
   
    // Determine the viewId by inspecting the URL
    // Can't be relative by the time we get here so don't check
    viewId = getViewIdFromPath(path);
   
    // TODO: Handle the case where this is a nonFaces (inprotocol) resource
    // I.e. viewId is null here.  Should we do something similar to
    // nonFaces support from 1.0?  I.e. bridge isn't expecting to handle?

    if (viewId != null)
    {
      // This is a Faces resource
      // put the viewId in the QueryStr.
      queryStr.addParameter(JSF_TARGET_VIEWID_RENDER_PARAMETER, viewId);
      queryStr.removeParameter(Bridge.PORTLET_MODE_PARAMETER);
      queryStr.removeParameter(Bridge.PORTLET_WINDOWSTATE_PARAMETER);
    }
   

    // Encode the URL
   
    ResourceURL resource = ((MimeResponse) mPortletResponse).createResourceURL();
    resource.setResourceID(path);
   
    // Walk through the queryStr Params and add as resourceParams
    // remove any attempt to set Mode/WindowState/etc. as
    // not feasible here
    // Add parameters so they don't get lost
    Enumeration<String> list = queryStr.getParameterNames();
    while (list.hasMoreElements())
    {
      String param = list.nextElement().toString();
      if (param.equals(Bridge.PORTLET_MODE_PARAMETER))
      {
        // do nothing -- just ignore -- can't encode in a resourceURL
      }
      else if (param.equals(Bridge.PORTLET_WINDOWSTATE_PARAMETER))
      {
        // do nothing -- just ignore -- can't encode in a resourceURL
      }
      else if (param.equals(Bridge.PORTLET_SECURE_PARAMETER))
      {
        try
        {
          resource.setSecure(Boolean.getBoolean(queryStr.getParameter(param)));
        }
        catch (Exception e)
        {
          ; // do nothing -- just ignore
        }
      }
      else
      {
        resource.setParameter(param, queryStr.getParameter(param));
      }
    }

    return portletURLToString(resource, isStrictXhtmlEncoded);
  }

  @Override
  public void dispatch(String requestURI)
    throws IOException, FacesException
  {
    if (requestURI == null)
    {
      throw new java.lang.NullPointerException();
    }

    PortletRequestDispatcher prd = mPortletContext.getRequestDispatcher(requestURI);

    if (prd == null)
    {
      throw new IllegalArgumentException("No request dispatcher can be created for the specified path: " +
                                         requestURI);
    }

    try
    {
      Boolean hasRenderRedirectedAfterForward = (Boolean) getRequestMap().get(BridgeImpl.HAS_RENDER_REDIRECTED_AFTER_FORWARD);
      if (hasRenderRedirectedAfterForward == null || !hasRenderRedirectedAfterForward.booleanValue())
      {
        prd.forward((PortletRequest) getRequest(), (PortletResponse) getResponse());
      }
      else
      {
        // during render redirect (following a dispatch forward) use include as the prior forward requires the reponse be marked as committed
        prd.include((PortletRequest) getRequest(), (PortletResponse) getResponse());
      }
    }
    catch (PortletException e)
    {
      if (e.getMessage() != null)
      {
        throw new FacesException(e.getMessage(), e);
      }
      else
      {
        throw new FacesException(e);
      }
    }
  }

  @Override
  public Object getSession(boolean create)
  {
    return mPortletRequest.getPortletSession(create);
  }

  @Override
  public Object getContext()
  {
    return mPortletContext;
  }

  @Override
  public Object getRequest()
  {
    return mPortletRequest;
  }

  @Override
  public Object getResponse()
  {
    if (mTempNonPortletResponse != null)
    {
      return mTempNonPortletResponse;
    }
    else
    {
      return mPortletResponse;
    }
  }

  @Override
  public Map<String, Object> getApplicationMap()
  {
    if (mApplicationMap == null)
    {
      mApplicationMap = new PortletApplicationMap(mPortletContext);
    }
    return mApplicationMap;
  }

  @Override
  public Map<String, Object> getSessionMap()
  {
    if (mSessionMap == null)
    {
      mSessionMap = new PortletSessionMap(mPortletRequest);
    }
    return mSessionMap;
  }

  @Override
  public Map<String, Object> getRequestMap()
  {
    if (mRequestMap == null)
    {
      mRequestMap = new PortletRequestMap(mPortletRequest);
    }
    return mRequestMap;
  }

  @Override
  public Map<String, String> getRequestParameterMap()
  {
    if (mRequestParameterMap == null)
    {
      //  Faces 1.2 allows a request to override the default renderkit id through use
      // of a 'known' request parameter.  We use this to allow each portlet in a app
      // to configure their own default.
     addDefaultRenderKitIdToRequest()
     
      // In cases where the viewId is derived from either an attribute set by the portlet
      // or from the defaultView Map, the viewId can contain additional querystring parameters.
      // These are held by the ExternalContext in an internal temp Map until this method
      // is called.
      mRequestParameterMap =
          Collections.unmodifiableMap(new PortletRequestParameterMap(mPortletRequest,
                                                                     mTempExtraRequestParameterMap));
      // Now that it has been used/added -- clear the temp holder as its no longer needed
      mTempExtraRequestParameterMap = null;
    }
    return mRequestParameterMap;
  }

  public Map<String, String[]> getRequestParameterValuesMap()
  {
    if (mRequestParameterValuesMap == null)
    {
      //  Faces 1.2 allows a request to override the default renderkit id through use
      // of a 'known' request parameter.  We use this to allow each portlet in a app
      // to configure their own default.
      addDefaultRenderKitIdToRequest();
     
      // In cases where the viewId is derived from either an attribute set by the portlet
      // or from the defaultView Map, the viewId can contain additional querystring parameters.
      // These are held by the ExternalContext in an internal temp Map until this method
      // is called.
      mRequestParameterValuesMap =
          Collections.unmodifiableMap(new PortletRequestParameterValuesMap(mPortletRequest,
                                                                           mTempExtraRequestParameterValuesMap));
      // Now that it has been used/added -- clear the temp holder as its no longer needed
      mTempExtraRequestParameterValuesMap = null;
    }
    return mRequestParameterValuesMap;
  }

  public Iterator<String> getRequestParameterNames()
  {
    //Map is unmodifiable, so the iterator will be as well
    return getRequestParameterMap().keySet().iterator();
  }

  public Map<String, String> getRequestHeaderMap()
  {
    if (mRequestHeaderMap == null)
    {
      if (mPortletRequestHeaders == null)
      {
        mPortletRequestHeaders = new PortletRequestHeaders(mPortletRequest);
      }

      mRequestHeaderMap = new PortletRequestHeaderMap(mPortletRequestHeaders);
    }
    return mRequestHeaderMap;
  }

  @Override
  public Map<String, String[]> getRequestHeaderValuesMap()
  {
    if (mRequestHeaderValuesMap == null)
    {
      if (mPortletRequestHeaders == null)
      {
        mPortletRequestHeaders = new PortletRequestHeaders(mPortletRequest);
      }

      mRequestHeaderValuesMap = new PortletRequestHeaderValuesMap(mPortletRequestHeaders);
    }
    return mRequestHeaderValuesMap;
  }

  @Override
  public Map<String, Object> getRequestCookieMap()
  {
    Map<String, Object> dummy = Collections.emptyMap();
    return dummy;
  }

  @Override
  public Locale getRequestLocale()
  {
    return mPortletRequest.getLocale();
  }

  @Override
  public String getRequestPathInfo()
  {
    // Delay computing the ViewId until someone needs it
    if (mViewId == null)
    {
      determineView(true);
    }
   
    return mPathInfo;
  }

  @Override
  public String getRequestContextPath()
  {
    return mPortletRequest.getContextPath();
  }

  @Override
  public String getInitParameter(String s)
  {
    return mPortletContext.getInitParameter(s);
  }

  @Override
  public Map<String, String> getInitParameterMap()
  {
    if (mInitParameterMap == null)
    {
      mInitParameterMap = new PortletInitParameterMap(mPortletContext);
    }
    return mInitParameterMap;
  }

  @SuppressWarnings("unchecked")
  public Set<String> getResourcePaths(String s)
  {
    return mPortletContext.getResourcePaths(s);
  }

  public InputStream getResourceAsStream(String s)
  {
    return mPortletContext.getResourceAsStream(s);
  }

  public String encodeNamespace(String s)
  {
    // Supposedly if this attribute is present we are running in Liferay
    if (getRequestMap().get("THEME_DISPLAY") == null)
    {
      return ((PortletResponse) mPortletResponse).getNamespace() + s;
    }
    else
    {
      // For liferay -- prefix with extra chars so they don't strip
      return LIFERAY_NAMESPACE_PREFIX_HACK + ((PortletResponse) mPortletResponse).getNamespace() + s;
    }
  }

  @Override
  public String getRequestServletPath()
  {
    // Delay computing the ViewId until someone needs it
    if (mViewId == null)
    {
      determineView(true);
    }
   
    return mServletPath;
  }

  @Override
  public String getAuthType()
  {
    return mPortletRequest.getAuthType();
  }

  @Override
  public String getRemoteUser()
  {
    return mPortletRequest.getRemoteUser();
  }

  @Override
  public boolean isUserInRole(String role)
  {
    return mPortletRequest.isUserInRole(role);
  }

  @Override
  public Principal getUserPrincipal()
  {
    return mPortletRequest.getUserPrincipal();
  }

  @Override
  public void log(String message)
  {
    mPortletContext.log(message);
  }

  @Override
  public void log(String message, Throwable t)
  {
    mPortletContext.log(message, t);
  }

  @SuppressWarnings("unchecked")
  @Override
  public Iterator<Locale> getRequestLocales()
  {
    //TODO: Cache this value...
    return new EnumerationIterator<Locale>(mPortletRequest.getLocales());
  }

  @Override
  public URL getResource(String s)
    throws MalformedURLException
  {
    return mPortletContext.getResource(s);
  }

  // Start of JSF 1.2 API

  /**
   * <p>
   * Set the environment-specific request to be returned by subsequent calls to {@link #getRequest}.
   * This may be used to install a wrapper for the request.
   * </p>
   *
   * <p>
   * The default implementation throws <code>UnsupportedOperationException</code> and is provided
   * for the sole purpose of not breaking existing applications that extend this class.
   * </p>
   *
   *
   * @since 1.2
   */
  @Override
  public void setRequest(Object request)
  {
    mPortletRequest = (PortletRequest) request;

    // clear out request based cached maps
    mRequestMap = null;
    mRequestParameterMap = null;
    mRequestParameterValuesMap = null;
    mRequestHeaderMap = null;
    mRequestHeaderValuesMap = null;
   
    // if already computed recalculate the view in case it has changed
    // One case where it changes is when a redirect occurs
    // during a render and we impl as a direct navigation
    if (mViewId != null)
    {
      determineView(false);
    }
  }

  /**
   *
   * <p>
   * Overrides the name of the character encoding used in the body of this request.
   * </p>
   *
   * <p>
   * Calling this method after the request has been accessed will have no no effect, unless a
   * <code>Reader</code> or <code>Stream</code> has been obtained from the request, in which
   * case an <code>IllegalStateException</code> is thrown.
   * </p>
   *
   * <p>
   * <em>Servlet:</em> This must call through to the <code>javax.servlet.ServletRequest</code>
   * method <code>setCharacterEncoding()</code>.
   * </p>
   *
   * <p>
   * <em>Portlet:</em> This must call through to the <code>javax.portlet.ActionRequest</code>
   * method <code>setCharacterEncoding()</code>.
   * </p>
   *
   * <p>
   * The default implementation throws <code>UnsupportedOperationException</code> and is provided
   * for the sole purpose of not breaking existing applications that extend this class.
   * </p>
   *
   * @throws java.io.UnsupportedEncodingException
   *           if this is not a valid encoding
   *
   * @since 1.2
   *
   */
  @Override
  public void setRequestCharacterEncoding(String encoding)
    throws UnsupportedEncodingException, IllegalStateException
  {
    if (getPortletPhase() != null && mPortletRequest instanceof ClientDataRequest)
    {
      try
      {
        ((ClientDataRequest) mPortletRequest).setCharacterEncoding(encoding);
      } catch (IllegalStateException e)
      {
        // Swallow this exception and proceed as if a noop.
      }
    }
   
    // Though can only do this operation in a PortletAction Faces impls have
    // been known to cache the encoding in memory and set in situations when
    // the request doesn't specify -- turns out this code can get hit during
    // the bridge render phase -- so if we threw an exception Faces would break. 
    // Just do a noop instead as render isn't supposed to receive parameters
    }

  /**
   *
   * <p>
   * Return the character encoding currently being used to interpret this request.
   * </p>
   *
   * <p>
   * <em>Servlet:</em> This must return the value returned by the
   * <code>javax.servlet.ServletRequest</code> method <code>getCharacterEncoding()</code>.
   * </p>
   *
   * <p>
   * <em>Portlet:</em> This must return the value returned by the
   * <code>javax.portlet.ActionRequest</code> method <code>getCharacterEncoding()</code>.
   * </p>
   *
   * <p>
   * The default implementation throws <code>UnsupportedOperationException</code> and is provided
   * for the sole purpose of not breaking existing applications that extend this class.
   * </p>
   *
   * @since 1.2
   *
   */
  @Override
  public String getRequestCharacterEncoding()
  {
    if (getPortletPhase() != null && mPortletRequest instanceof ClientDataRequest)
    {
      return ((ClientDataRequest) mPortletRequest).getCharacterEncoding();
    }
    else
    {
      // other phases -- return null as per spec
      return null;
    }
  }

  /**
   *
   * <p>
   * Return the MIME Content-Type for this request. If not available, return <code>null</code>.
   * </p>
   *
   * <p>
   * <em>Servlet:</em> This must return the value returned by the
   * <code>javax.servlet.ServletRequest</code> method <code>getContentType()</code>.
   * </p>
   *
   * <p>
   * <em>Portlet:</em> This must return <code>null</code>.
   * </p>
   *
   * NOTE: We are deviating from the javadoc based on recommendation from JSR 301 expert group
   *
   * <p>
   * The default implementation throws <code>UnsupportedOperationException</code> and is provided
   * for the sole purpose of not breaking existing applications that extend this class.
   * </p>
   *
   * @since 1.2
   */
  @Override
  public String getRequestContentType()
  {
    if (getPortletPhase() != null && mPortletRequest instanceof ClientDataRequest)
    {
      return ((ClientDataRequest) mPortletRequest).getContentType();
    }
    else
    {
      // Other PHASEs: return null as per spec
      return null;
    }
  }

  /**
   *
   * <p>
   * Returns the name of the character encoding (MIME charset) used for the body sent in this
   * response.
   * </p>
   *
   * <p>
   * <em>Servlet:</em> This must return the value returned by the
   * <code>javax.servlet.ServletResponse</code> method <code>getCharacterEncoding()</code>.
   * </p>
   *
   * <p>
   * <em>Portlet:</em> This must return <code>null</code>.
   * </p>
   *
   * NOTE: We are deviating from the javadoc based on recommendation from JSR 301 expert group
   *
   * <p>
   * The default implementation throws <code>UnsupportedOperationException</code> and is provided
   * for the sole purpose of not breaking existing applications that extend this class.
   * </p>
   *
   * @since 1.2
   */
  @Override
  public String getResponseCharacterEncoding()
  {
    if (!(mPortletResponse instanceof MimeResponse))
    {
      throw new IllegalStateException("PortletExternalContextImpl.getResponseCharacterEncoding(): Response must be a MimeResponse");
    }

    return ((MimeResponse) mPortletResponse).getCharacterEncoding();
  }

  /**
   *
   * <p>
   * Return the MIME Content-Type for this response. If not available, return <code>null</code>.
   * </p>
   *
   * <p>
   * <em>Servlet:</em> This must return the value returned by the
   * <code>javax.servlet.ServletResponse</code> method <code>getContentType()</code>.
   * </p>
   *
   * <p>
   * <em>Portlet:</em> This must return <code>null</code>.
   * </p>
   *
   * NOTE: We are deviating from the javadoc based on recommendation from JSR 301 expert group
   *
   * <p>
   * The default implementation throws <code>UnsupportedOperationException</code> and is provided
   * for the sole purpose of not breaking existing applications that extend this class.
   * </p>
   *
   * @since 1.2
   */
  @Override
  public String getResponseContentType()
  {
    if (!(mPortletResponse instanceof MimeResponse))
    {
      throw new IllegalStateException("PortletExternalContextImpl.getResponseContentType(): Response must be a MimeResponse");
    }

    return ((MimeResponse) mPortletResponse).getContentType();
  }

  /**
   * <p>
   * Set the environment-specific response to be returned by subsequent calls to
   * {@link #getResponse}. This may be used to install a wrapper for the response.
   * </p>
   *
   * <p>
   * The default implementation throws <code>UnsupportedOperationException</code> and is provided
   * for the sole purpose of not breaking existing applications that extend this class.
   * </p>
   *
   *
   * @since 1.2
   */
  @Override
  public void setResponse(Object response)
  {
    // Early versions of Mojarra 1.2 check the externalContext response object to see if it implements its writeBehind API (using instanceof)
    // And since protlet 1.0 didn't support portlet filters/wrappers the bridge allows one to implement this via a servlet filter/wrapper.
    // In such circumstances the Mojarra servlet response wrapper has to be set as the current externalContext response so that things run properly
    // Hence we need to account/allow for non-portlet response objects here (temporarily)
    if (response instanceof PortletResponse)
    {
      mPortletResponse = (PortletResponse) response;
      // clear if set so its not used in getResponse()
      mTempNonPortletResponse = null;
    }
    else
    {
      mTempNonPortletResponse = response;
    }
  }

  /**
   *
   * <p>
   * Sets the character encoding (MIME charset) of the response being sent to the client, for
   * example, to UTF-8.
   * </p>
   *
   * <p>
   * <em>Servlet:</em> This must call through to the <code>javax.servlet.ServletResponse</code>
   * method <code>setCharacterEncoding()</code>.
   * </p>
   *
   * <p>
   * <em>Portlet:</em> This method must take no action.
   * </p>
   *
   * <p>
   * The default implementation throws <code>UnsupportedOperationException</code> and is provided
   * for the sole purpose of not breaking existing applications that extend this class.
   * </p>
   *
   *
   * @since 1.2
   *
   */
  @Override
  public void setResponseCharacterEncoding(String encoding)
  {
    // JSR 168 has no corresponding API.
  }

  // End of JSF 1.2 API
 
  private void debugLog(String message)
  {
    Boolean loggingEnabled = (Boolean) getRequestMap().get(BridgeImpl.LOGGING_ENABLED);
    if (loggingEnabled != null && loggingEnabled.booleanValue())
    {
      log(message);
    }
  }
  
  private void addDefaultRenderKitIdToRequest()
  {
    if (mDefaultRenderKitId == null) return;
   
    if (mPortletRequest.getParameter(ResponseStateManager.RENDER_KIT_ID_PARAM) != null)
    {
      // request already has one
      return;
    }
   
    if (mTempExtraRequestParameterMap == null)
    {
      mTempExtraRequestParameterMap = (Map<String, String>) new HashMap(1);
    }
    if (mTempExtraRequestParameterMap.get(ResponseStateManager.RENDER_KIT_ID_PARAM) == null)
    {
      mTempExtraRequestParameterMap.put(ResponseStateManager.RENDER_KIT_ID_PARAM, mDefaultRenderKitId);
    }
   
    if (mTempExtraRequestParameterValuesMap == null)
    {
      mTempExtraRequestParameterValuesMap = (Map<String, String[]>) new HashMap(1);
    }
    if (mTempExtraRequestParameterValuesMap.get(ResponseStateManager.RENDER_KIT_ID_PARAM) == null)
    {
      mTempExtraRequestParameterValuesMap.put(ResponseStateManager.RENDER_KIT_ID_PARAM, new String[] {mDefaultRenderKitId});
    }

  }

  private Bridge.PortletPhase getPortletPhase()
  {
    return (Bridge.PortletPhase) getRequestMap().get(Bridge.PORTLET_LIFECYCLE_PHASE);
  }

  /**
   * Gets the view identifier we should use for this request.
   */
  private String getViewId(boolean updateHistory)
    throws BridgeDefaultViewNotSpecifiedException, BridgeInvalidViewPathException
  {
    boolean modeChanged = false;
    String requestedMode = mPortletRequest.getPortletMode().toString();
   
    // See if the portlet has specified the target view
    String viewId = (String) mPortletRequest.getAttribute(Bridge.VIEW_ID);
    String viewPath = null;
    if (viewId == null)
    {
      viewPath = (String) mPortletRequest.getAttribute(Bridge.VIEW_PATH);
      if (viewPath != null)
      {
        //convert the view path into a viewId
        viewId = getViewIdFromPath(viewPath);
        if (viewId == null)
        {
          throw new BridgeInvalidViewPathException("Unable to resolve faces viewId: "
              + viewPath);
        }
      }
    }
     
    // Normal case is its returned in the render parameter
    if (viewId == null)
    {
     // Read the target from the request parameter
       
      viewId = mPortletRequest.getParameter(JSF_TARGET_VIEWID_RENDER_PARAMETER);

      if (viewId != null) debugLog("PortletExternalContextImpl.getViewId: found jsf target viewId = " + viewId);

      // ViewIds stored in RenderParams are encoded with the Mode to which they apply
      // Ensure current request Mode matches before using the viewId portion
      if (viewId != null)
      {
        int i = viewId.indexOf(':');
        if (i >= 0 )
        {
     
          String mode = viewId.substring(0, i);
          viewId = viewId.substring(i+1);
          if (!mode.equalsIgnoreCase(requestedMode))
          {
            modeChanged = true;
            viewId = null; // didn't match so don't use it
          }
        }
      }
    }
    if (viewId == null)
    {
      Map<String, String> m = (Map<String,String>) mPortletContext.getAttribute(
                                Bridge.BRIDGE_PACKAGE_PREFIX + mPortletName
                                      + "." + Bridge.DEFAULT_VIEWID_MAP);
      viewId = m.get(requestedMode);
      if (viewId == null)
      {
        // If no defaultview then throw an exception
        throw new BridgeDefaultViewNotSpecifiedException();
      }

      debugLog("PortletExternalContextImpl.getViewId: jsf target viewId not found, defaulting to: " + viewId);
    }

    // Some viewId may have query string, so handle that here
    // (e.g., TaskFlow has the following viewId:
    // /adf.task-flow?_document=/WEB-INF/task-flow.xml&_id=task1

    int queryStart = viewId.indexOf('?');
    QueryString queryStr = null;

    if (queryStart != -1)
    {
      // parse the query string and add the parameters to internal maps
      // delay the creation of ParameterMap and ParameterValuesMap until
      // they are needed/called by the client
      queryStr = new QueryString(viewId.substring(queryStart + 1), "UTF8");

      // TODO: Constants
      // We store these into a temporary Map until a client calls the corresponding
      // ExternalContext public api to get the request parameter map(s).  In those
      // methods we use these temp maps to build the overall Map that is returned.
      // This roundabout technique is used to delay accessing request parameters until the
      // client first requests them.
      mTempExtraRequestParameterMap = new HashMap<String, String>(5);
      mTempExtraRequestParameterValuesMap = new HashMap<String, String[]>(5);
      // Clear any existing Request ParameterMap to ensure reconstruction
      // with these new extra entries.
      mRequestParameterMap = null;
      mRequestParameterValuesMap = null;

      Enumeration<String> list = queryStr.getParameterNames();
      while (list.hasMoreElements())
      {
        String param = list.nextElement();
        mTempExtraRequestParameterMap.put(param, queryStr.getParameter(param));
       
        // Now deal with the multiValue case
        Enumeration<String> e = queryStr.getParameterValues(param);
        ArrayList<String> l = new ArrayList(5);
        while (e.hasMoreElements())
        {
          l.add(e.nextElement());        
        }
        String[] values = new String[l.size()];
        mTempExtraRequestParameterValuesMap.put(param, l.toArray(values));
      }

      viewId = viewId.substring(0, queryStart);
      debugLog("PortletExternalContextImpl.getViewId: special viewId: " + viewId);
    }

    // before returning -- update the appropriate session attr with the current
    // mode/viewid info so developers can access via EL when they want to navigate
    // between modes and end up in the last view of the new mode
    // Note: only do in render phase becase we need to remember the
    // render parameters -- during a render all parameters are render parameters
    if (updateHistory && getPortletPhase() == Bridge.PortletPhase.RENDER_PHASE)
    {
      updateViewChainAttribute(mPortletRequest.getPortletMode().toString(), viewId, modeChanged);
    }

    // If the mode changes mark this so BridgeImpl (controller) can see it shouldn't
    // restore/save the scope
    this.getRequestMap().put(NO_SCOPE, Boolean.valueOf(modeChanged));

    return viewId;
  }

  private void updateViewChainAttribute(String mode, String viewId, boolean modeChanged)
  {
    QueryString qs = new QueryString("UTF8");
   
    // always encode the mode in the viewId as this is used for mode transitions
    qs.setParameter(Bridge.PORTLET_MODE_PARAMETER, mode);
   
    if (!modeChanged)
    {
        // Build a QueryString from the request's render parameters so can preserve
      // with the viewId
      Map m = getRequestParameterValuesMap();
      if (!m.isEmpty())
      {
        Set <Map.Entry<String, String[]>> set = m.entrySet();
        Iterator <Map.Entry<String,String[]>> i = set.iterator();
        while (i.hasNext()) 
        {
          Map.Entry<String,String[]> e = i.next();
          // only add if not a viewId or viewState parameter
          if (!e.getKey().equals(JSF_TARGET_VIEWID_RENDER_PARAMETER)
          && !e.getKey().equals(ResponseStateManager.VIEW_STATE_PARAM))
          {
            for (String s : e.getValue())
            {
              qs.addParameter(e.getKey(), s);
            }
          }
        }
      }
    }
       
    String toAppend = qs.toString();
    StringBuffer sb = new StringBuffer(viewId.length() + toAppend.length() + 1);
    viewId = sb.append(viewId).append("?").append(toAppend).toString();
     
    // Now add to the appropriate session attribute based on Mode
    Map sessionMap = getSessionMap();
    StringBuffer key = new StringBuffer(100);
    sessionMap.put(key.append(Bridge.VIEWID_HISTORY).append('.').append(mode).toString(), viewId);
   
    debugLog("History for mode: " + mode + " : " + viewId);
  }

  private String replaceResourceQueryStringMarkers(String s)
  {
    String path = null;
    QueryString queryStr = null;
    int queryStart = -1;

    // First: split URL into path and query string
    // Hold onto QueryString for later processing
    queryStart = s.indexOf('?');

    // references aren't in the querystring so nothing to do
    if (queryStart == -1)
      return s;

    FacesContext context = FacesContext.getCurrentInstance();

    queryStr = new QueryString(s.substring(queryStart + 1), "UTF8");
    path = s.substring(0, queryStart);

    try
    {
      // If there is a backlink -- remove and convert it
      String backLinkValue = queryStr.removeParameter(Bridge.BACK_LINK);
      if (backLinkValue!= null)
      {
        queryStr.setParameter(backLinkValue, encodeActionURL(context.getApplication().getViewHandler().getActionURL(context,
                                context.getViewRoot().getViewId())), false);
      }
      // just remove the viewLink param
      queryStr.removeParameter(Bridge.VIEW_LINK);
    }
    catch (Exception e)
    {
      ; // do nothing -- just ignore
   

    // Now put the string back together
    String qs = queryStr.toString();
    if (qs.length() > 0)
    {
      s = path + "?" + qs;
    }
    else
    {
      s = path;
    }
   
    return s;
  }

  private void mapPathsFromViewId(String viewId, List<String> mappings)
  {
    if (viewId == null || mappings == null)
    {
      // Fail safe -- even if we didn't find a servlet mapping set path
      // info
      // as if we did as this value is all anything generally depends on
      mPathInfo = viewId;
      return;
    }

    // The only thing that matters is we use a configured mapping
    // So just use the first one
    String mapping = mappings.get(0);
    if (mapping.startsWith("*"))
    {
      // we are using suffix mapping
      viewId =
          viewId.substring(0, viewId.lastIndexOf('.')) + mapping.substring(mapping.indexOf('.'));

      // we are extension mapped
      mServletPath = viewId;
      mPathInfo = null;

      // Workaround Faces RI that has Servlet dependencies if this isn't set
      if (mUseIncludeAttributeServletDependencyWorkaround)
        mPortletRequest.setAttribute(SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE, mServletPath);
    }
    else
    {
      // we are using prefix mapping
      int j = mapping.lastIndexOf("/*");
      if (j != -1)
      {
        mServletPath = mapping.substring(0, j);
      }
      else
      {
        // is it valid to omit the trailing /*????
        mServletPath = mapping;
      }

      // Fail safe -- even if we didn't find a servlet mapping set path info
      // as if we did as this value is all anything generally depends on
      mPathInfo = viewId;
    }

  }

  private String extensionMappingFromViewId(String viewId)
  {
    // first remove/ignore any querystring
    int i = viewId.indexOf('?');
    if (i != -1)
    {
      viewId = viewId.substring(0, i);
    }

    int extLoc = viewId.lastIndexOf('.');

    if (extLoc != -1 && extLoc > viewId.lastIndexOf('/'))
    {
      StringBuilder sb = new StringBuilder("*");
      sb.append(viewId.substring(extLoc));
      return sb.toString();
    }
    return null;
  }

  private String getViewIdFromPath(String url)
  {
    // Get a string that holds the path after the Context-Path through the
    // target

    // First remove the query string
    int i = url.indexOf("?");
    if (i != -1)
    {
      url = url.substring(0, i);
    }

    // Now remove up through the ContextPath
    String ctxPath = getRequestContextPath();
    i = url.indexOf(ctxPath);
    if (i != -1)
    {
      url = url.substring(i + ctxPath.length());
    }

    String viewId = null;
    // Okay now figure out whether this is prefix or suffixed mapped
    if (isSuffixedMapped(url, mFacesMappings))
    {
      viewId =
          viewIdFromSuffixMapping(url, mFacesMappings, mPortletContext.getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME));
    }
    else if (isPrefixedMapped(url, mFacesMappings))
    {
      viewId = viewIdFromPrefixMapping(url, mFacesMappings);
    }
    else
    {
      // Not a Faces URL
      viewId = null;
    }
    return viewId;
  }

  private boolean isSuffixedMapped(String url, List<String> mappings)
  {
    // see if the viewId terminates with an extension
    // if non-null value contains *.XXX where XXX is the extension
    String ext = extensionMappingFromViewId(url);
    return ext != null && mappings.contains(ext);
  }

  private String viewIdFromSuffixMapping(String url, List<String> mappings, String ctxDefault)
  {
    // replace extension with the DEFAULT_SUFFIX
    if (ctxDefault == null)
    {
      ctxDefault = ViewHandler.DEFAULT_SUFFIX;
    }

    int i = url.lastIndexOf(".");
    if (ctxDefault != null && i != -1)
    {
      if (ctxDefault.startsWith("."))
      {
        url = url.substring(0, i) + ctxDefault;
      }
      else
      {
        // shouldn't happen
        url = url.substring(0, i) + "." + ctxDefault;
      }
    }
    return url;
  }

  private boolean isPrefixedMapped(String url, List<String> mappings)
  {
    for (int i = 0; i < mappings.size(); i++)
    {
      String prefix = null;
      String mapping = mappings.get(i);
      if (mapping.startsWith("/"))
      {
        int j = mapping.lastIndexOf("/*");
        if (j != -1)
        {
          prefix = mapping.substring(0, j);
        }
      }
      if (prefix != null && url.startsWith(prefix))
      {
        return true;
      }
    }
    return false;
  }
 
  private void determineView(boolean updateHistory)
  {
    // viewId is the actual context relative path to the resource
    String viewId = getViewId(updateHistory);
   
    if (mViewId != null && viewId.equals((mViewId)))
    {
      return// No change
    }
   
    mViewId = viewId;

    // Now reverse engineer the servlet paths from the mappings
    // So Faces thinks was a client request
    mapPathsFromViewId(mViewId, mFacesMappings);

    // JSF RI relies on a request attribute setting to properly handle
    // suffix mapping -- but because their suffix mapping code is servlet dependent
    // we need to set it for them
    setFacesMapping();
  }

  private String viewIdFromPrefixMapping(String url, List<String> mappings)
  {
    for (int i = 0; i < mappings.size(); i++)
    {
      String prefix = null;
      String mapping = mappings.get(i);
      if (mapping.startsWith("/"))
      {
        int j = mapping.lastIndexOf("/*");
        if (j != -1)
        {
          prefix = mapping.substring(0, j);
        }
      }
      if (prefix != null && url.startsWith(prefix))
      {
        return url.substring(prefix.length());
      }
    }
    return null;
  }

  private void setFacesMapping()
  {
    String mapping = null;
    String servletPath = this.getRequestServletPath();

    // if PathInfo == null we are suffixed mapped
    if (this.getRequestPathInfo() == null)
    {
      mapping = servletPath.substring(servletPath.lastIndexOf('.'));

    }
    else
    {
      mapping = servletPath;
    }

    // RI can only calculate mapping if a servlet request
    // so set the attribute they expect with the appropriate info.
    this.getRequestMap().put("com.sun.faces.INVOCATION_PATH", mapping);
  }

  private boolean isPortletURL(String url)
  {
    // Quick check for most common case
    return url.toLowerCase().startsWith("portlet:");
  }
 
  private String portletURLToString(BaseURL baseURL, boolean xhtmlEscape)
  {
    StringWriter sw = new StringWriter(30);
   
    try
    {
      baseURL.write(sw, xhtmlEscape);
      return sw.toString();
    } catch (Exception e)
    {
      return baseURL.toString();
    }
  }
 
  private String xhtmlEncode(String url)
  {
    StringBuffer sb = new StringBuffer(url.length());
   
    // check for use of &amp; in query string
    int copyFrom = 0;
    int currentPos = url.indexOf('?');

    if (currentPos == -1) return url;
   
    while (true)
    {
      int ampPos = url.indexOf('&', currentPos);
      int xhtmlAmpPos = url.indexOf("&amp;", currentPos);
      // no more & to process -- so return current value of isStrict
      if (ampPos == -1)
      {
        if (copyFrom < url.length())
        {
          sb.append(url, copyFrom, url.length());
        }
        return sb.toString();
      }
      // if the amp we found doesn't start an &amp; then its not strict
      if (ampPos != xhtmlAmpPos)
      {
        sb.append(url, copyFrom, ampPos);
        sb.append("&amp;");
        copyFrom = ampPos + 1;
      }
      currentPos = ampPos + 1;
    }   
  }

  private boolean isStrictXhtmlEncoded(String url)
  {
    // check for use of &amp; in query string
    int currentPos = url.indexOf('?');

    if (currentPos == -1) return false;
   
    boolean isStrict = false;   
    while (true)
    {
      int ampPos = url.indexOf('&', currentPos);
      int xhtmlAmpPos = url.indexOf("&amp;", currentPos);
      // no more & to process -- so return current value of isStrict
      if (ampPos == -1)
      {
        return isStrict;
      }
      // if the amp we found doesn't start an &amp; then its not strict
      if (ampPos != xhtmlAmpPos)
      {
        return false;
      }
      isStrict = true;
      currentPos = ampPos + 1;
    }
  }


  private boolean isAbsoluteURL(String url)
  {
    // Quick check for most common case
    url = url.toLowerCase();
    if (url.startsWith("http:") || url.startsWith("https:"))
    {
      return true;
    }

    // now deal with other possible protocols -- find the potential scheme
    int i = url.indexOf(":");
    if (i == -1)
    {
      return false;
    }

    // Now make sure that the  substring before the : is a valid scheme
    // i.e. contains no URI reserved characters
    String scheme = url.substring(0, i);

    if (scheme.indexOf(";") != -1)
      return false;
    else if (scheme.indexOf("/") != -1)
      return false;
    else if (scheme.indexOf("#") != -1)
      return false;
    else if (scheme.indexOf("?") != -1)
      return false;
    else if (scheme.indexOf(" ") != -1)
      return false;
    else
      return true;
  }
 
  private boolean isOpaqueURL(String url)
  {
    if (!isAbsoluteURL(url))
    {
      return false;
    }
   
    // Its opaque if it is absolute (contains a scheme) that isn't followed
    // by a '/' or doesn't start with our special portlet: scheme
    return (!isPortletURL(url) && (url.indexOf(':') != (url.indexOf('/') - 1)));
  }

  private boolean isExternalURL(String url)
  {
    if (!isAbsoluteURL(url))
    {
      return false;
    }

    // otherwise see if the URL contains the ContextPath

    // Simple test is that the url doesn't contain
    // the CONTEXT_PATH -- though ultimately may want to test
    // if we are on the same server
    String ctxPath = getRequestContextPath();
    int i = url.indexOf(ctxPath);
    int j = url.indexOf("?");
    if (i != -1 && (j == -1 || i < j))
    {
      return false;
    }
    return true;
  }
 
  private boolean isFacesURL(String url)
  {
    String viewId = null;
   
    // Determine the viewId by inspecting the URL
    // Convert relative path to context path
    if (isRelativePath(url))
    {
      url = getPathFromRelativePath(url);
    }
   
    // Now se if this is a Faces URL
    viewId = getViewIdFromPath(url);

    return viewId != null;
  }
 
  private boolean isDirectLink(String url)
  {
    return isTokenLink(Bridge.DIRECT_LINK, url);
  }
 
  private boolean isInProtocolResourceLink(String url)
  {
    // its in protocol if either it contains the special marker or the URL
    // references a Faces view/resource.
    if (isTokenLink(Bridge.IN_PROTOCOL_RESOURCE_LINK, url))
    {
      return true;
    }
    else
    {
      return isFacesURL(url);
    }
   
  }
 
 
  private boolean isViewLink(String url)
  {
    return isTokenLink(Bridge.VIEW_LINK, url);
  }
 
  private boolean isTokenLink(String token, String url)
  {
    int queryStart = url.indexOf('?');
    QueryString queryStr = null;
    String tokenParam = null;

    if (queryStart != -1)
    {
      queryStr = new QueryString(url.substring(queryStart + 1), "UTF8");
      tokenParam = queryStr.getParameter(token);
      return Boolean.parseBoolean(tokenParam);
    }

    return false;
  }

  private String removeDirectLink(String url)
  {
    return removeTokenLink(Bridge.DIRECT_LINK, url);
  }
 
  private String removeInProtocolResourceLink(String url)
  {
    return removeTokenLink(Bridge.IN_PROTOCOL_RESOURCE_LINK, url);
  }
 
  private String removeTokenLink(String token, String url)
  {
    int queryStart = url.indexOf('?');
    QueryString queryStr = null;

    if (queryStart != -1)
    {
      queryStr = new QueryString(url.substring(queryStart + 1), "UTF8");
      queryStr.removeParameter(token);
      String query = queryStr.toString();
      if (query != null && query.length() != 0)
      {
        url = url.substring(0, queryStart + 1) + query;
      }
      else
      {
        url = url.substring(0, queryStart);
      }
    }

    return url;
  }
 
  private void encodeFacesActionTarget(QueryString queryStr, String target)
  {
    // determine mode that we need to encode with this viewId
    String mode = queryStr.getParameter(Bridge.PORTLET_MODE_PARAMETER);
    if (mode == null  || !mPortletRequest.isPortletModeAllowed(new PortletMode(mode)))
    {
      mode = mPortletRequest.getPortletMode().toString();
    }
    else if (!mode.equalsIgnoreCase(mPortletRequest.getPortletMode().toString()))
    {
      // mode changed -- scopes shouldn't be managed (saved/restored) when a mode changes
      // so mark here so the BrigeImpl (controller) knows what to do.
      this.getRequestMap().put(NO_SCOPE, Boolean.TRUE);
    }
   
    // put the viewId in the QueryStr.
    queryStr.setParameter(JSF_TARGET_VIEWID_RENDER_PARAMETER,
                          new StringBuffer(100).append(mode).append(':').append(target).toString());
  }
 
  private void encodeNonFacesActionTarget(QueryString queryStr, String target)
  {
    // whenever navigating to nonJSF target we don't need to manage scope
    this.getRequestMap().put(NO_SCOPE, Boolean.TRUE);
   
    // put the viewId in the QueryStr.
    queryStr.setParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER, target);
  }
 
  private String getAbsoluteUrlFromPath(String path)
  {
    if (isRelativePath(path))
    {
      path = getPathFromRelativePath(path);
    }
   
    // now add the absolute portion:
    StringBuffer sb = new StringBuffer(path.length() + 20);
    return sb.append(mPortletRequest.getScheme())
             .append("://")
             .append(mPortletRequest.getServerName())
             .append(":")
             .append(mPortletRequest.getServerPort())
             .append(path)
             .toString();
  }
 
  private String getPathFromRelativePath(String url)
  {
    String pathInfo = getRequestPathInfo();
    String servletPath = getRequestServletPath();
   
    String currentViewId = pathInfo;
    //  Get by combining the ServletPath with the PathIno
    if (servletPath != null)
    {
      if (pathInfo != null)
      {
        currentViewId = servletPath + pathInfo;
      }
      else
      {
        currentViewId = servletPath;
      }
    }

    // reduce to only the path portion
    String prefixURL = currentViewId.substring(0, currentViewId.lastIndexOf('/'));

    if (prefixURL.length() != 0 && !prefixURL.startsWith("/"))
    {
      // TODO: report/log an error
      return null; // this shouldn't happen, if so just return
    }

    if (url.startsWith("./"))
    {
      url = url.substring(2);
    }
    while (url.startsWith("../") && prefixURL.length() != 0)
    {
      url = url.substring(3);
      prefixURL = prefixURL.substring(0, prefixURL.lastIndexOf('/'));
    }
   
    return prefixURL + "/" + url;
  }

  private boolean isRelativePath(String url)
  {
   
    // relative path doesn't start with a '/'
    if (isAbsoluteURL(url) || url.startsWith("/"))
    {
      return false;
    }

    // relative path if starts with a '.' or doesn't contain a '/'
    if (url.startsWith("."))
    {
      return true;
    }

    // neither obviously a relative path or not -- now discount protocol
    int i = url.indexOf("://");
    if (i == -1)
    {
      return true;
    }

    // make sure : isn't in querystring
    int j = url.indexOf('?');
    if (j != -1 && j < i)
    {
      return true;
    }

    return false;
  }

}
TOP

Related Classes of org.apache.myfaces.portlet.faces.context.PortletExternalContextImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.