Package org.apache.myfaces.portlet.faces.bridge

Source Code of org.apache.myfaces.portlet.faces.bridge.BridgeImpl

/* 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.bridge;

import java.io.IOException;
import java.io.Serializable;

import java.lang.reflect.Method;

import java.net.URL;

import java.rmi.server.UID;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.el.ELContext;
import javax.el.ELContextEvent;
import javax.el.ELContextListener;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.application.FacesMessage;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.faces.render.ResponseStateManager;
import javax.faces.webapp.FacesServlet;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.ClientDataRequest;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.MimeResponse;
import javax.portlet.PortalContext;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import javax.portlet.StateAwareResponse;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.BridgeDefaultViewNotSpecifiedException;
import javax.portlet.faces.BridgeEventHandler;
import javax.portlet.faces.BridgeException;
import javax.portlet.faces.BridgeNotAFacesRequestException;
import javax.portlet.faces.BridgePublicRenderParameterHandler;
import javax.portlet.faces.BridgeUninitializedException;
import javax.portlet.faces.BridgeUtil;
import javax.portlet.faces.BridgeWriteBehindResponse;
import javax.portlet.faces.annotation.BridgePreDestroy;
import javax.portlet.faces.annotation.BridgeRequestScopeAttributeAdded;
import javax.portlet.faces.annotation.ExcludeFromManagedRequestScope;

import javax.portlet.faces.event.EventNavigationResult;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

import org.apache.myfaces.portlet.faces.bridge.wrapper.BridgePortletRequestWrapper;
import org.apache.myfaces.portlet.faces.bridge.wrapper.BridgeRenderRequestWrapper;
import org.apache.myfaces.portlet.faces.context.PortletExternalContextImpl;
import org.apache.myfaces.portlet.faces.el.PortletELContextImpl;
import org.apache.myfaces.portlet.faces.el.PortletELResolver;
import org.apache.myfaces.portlet.faces.util.QueryString;
import org.apache.myfaces.portlet.faces.util.config.FacesConfigurationProcessor;
import org.apache.myfaces.portlet.faces.util.config.WebConfigurationProcessor;

public class BridgeImpl
  implements Bridge, ELContextListener, PhaseListener, ServletRequestAttributeListener
{
  private static final long serialVersionUID = 5807626987246270989L;

  // public so PortletStateManager/ViewHandler can see/use
  public static final String UPDATED_VIEW_STATE_PARAM = "org.apache.myfaces.portlet.faces.updatedViewStateParam";
  public static final String REDIRECT_VIEWPARAMS = "org.apache.myfaces.portlet.faces.redirectViewParams";
  public static final String RENDER_REDIRECT_VIEWPARAMS = "org.apache.myfaces.portlet.faces.renderRedirectViewParams";
  public static final String RENDER_REDIRECT_PRPMAP = "org.apache.myfaces.portlet.faces.renderRedirectPRPMap";
  public static final String HAS_RENDER_REDIRECTED_AFTER_FORWARD = "org.apache.myfaces.portlet.faces.hasRenderRedirectedAfterForward";
 
  // public so other parts of the impl can access the portletName to access portlet specific context attrs
  public static final String PORTLET_NAME_ATTRIBUTE = "org.apache.myfaces.portlet.faces.portletName";
  public static final String VIEWID_QUERYSTRING_ATTRIBUTE = "org.apache.myfaces.portlet.faces.viewIdQueryString";

  private static final String REQUEST_SCOPE_LOCK = "org.apache.myfaces.portlet.faces.requestScopeLock";
  private static final String REQUEST_SCOPE_MAP = "org.apache.myfaces.portlet.faces.requestScopeMap";
  private static final String REQUEST_SCOPE_LISTENER = "org.apache.myfaces.portlet.faces.requestScopeWatch";
  public static final String FACES_VIEWROOT = "org.apache.myfaces.portlet.faces.includeInScope.facesViewRoot";
  private static final String FACES_MESSAGES = "org.apache.myfaces.portlet.faces.includeInScope.facesMessages";
  public static final String REQUEST_PARAMETERS = "org.apache.myfaces.portlet.faces.includeInScope.requestParameters";
  public static final String PREEXISTING_ATTRIBUTE_NAMES = "org.apache.myfaces.portlet.faces.preExistingAttributeNames";
  private static final String REQUEST_SCOPE_ID_RENDER_PARAM = "__jpfbReqScopeId";
  private static final String NULL_VIEW_STATE_PARAM_VALUE = "org.apache.myfaces.portlet.faces.nullViewState";
  private static final String CACHED_VIEWROOT_LOCALE = "org.apache.myfaces.portlet.faces.cachedViewRootLocale";
  private static final String PROCESSED_PUBLIC_PARAMS ="org.apache.myfaces.portlet.faces.processedPublicParams";
  private static final int DEFAULT_MAX_MANAGED_REQUEST_SCOPES = 100;

  private Boolean mPreserveActionParams = Boolean.FALSE;
  private List<String> mExcludedRequestAttributes = null;
  private Map<String, String>mPublicParameterMappings = null;

  private PortletConfig mPortletConfig = null;
  private FacesContextFactory mFacesContextFactory = null;
  private Lifecycle mLifecycle = null;
  private BridgeEventHandler mEventHandler = null;
  private BridgePublicRenderParameterHandler mProcessPRPHandler = null;
  private List<String> mFacesMappings = null;
  private boolean mInitialized = false;

  private Map<String,String> mDefaultViewIdMap = null;
 
  private Class<? extends BridgeWriteBehindResponse> mBridgeWriteBehindResponse = null;

  public BridgeImpl()
  {
    // everything gets done in the init call.
  }

  public void init(PortletConfig config)
    throws BridgeException
  {
    //TODO: Should we throw an exception if the bridge is already initialized?
    if (mInitialized)
      throw new BridgeException("Bridge already initialized.");

    mPortletConfig = config;
    PortletContext portletContext = mPortletConfig.getPortletContext();
   
    // acquire any config information in faces-config.xml
    FacesConfigurationProcessor processor = new FacesConfigurationProcessor(portletContext);
    mPublicParameterMappings = processor.getPublicParameterMappings();
   
    // get bridgeEventHandler, processPublicRenderParameterHandler, preserveActionParams, excludedAttributes configuration settings.
    mEventHandler = (BridgeEventHandler) portletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + mPortletConfig.getPortletName() +
                                              "." + Bridge.BRIDGE_EVENT_HANDLER);
   
    mProcessPRPHandler = (BridgePublicRenderParameterHandler) portletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + mPortletConfig.getPortletName() +
                                              "." + Bridge.BRIDGE_PUBLIC_RENDER_PARAMETER_HANDLER);
    mPreserveActionParams =
        (Boolean) portletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + mPortletConfig.getPortletName() +
                                              "." + Bridge.PRESERVE_ACTION_PARAMS);
   
    mBridgeWriteBehindResponse = (Class<? extends BridgeWriteBehindResponse>) portletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + mPortletConfig.getPortletName() +
                                              "." + Bridge.WRITE_BEHIND_RESPONSE);

    mExcludedRequestAttributes = (List<String>) portletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + mPortletConfig.getPortletName() +
                                                      "." + Bridge.EXCLUDED_REQUEST_ATTRIBUTES);
    if (mExcludedRequestAttributes != null)
    {
      // copy the list as we may be adding to it and don't want to worry that this might be immutable
      mExcludedRequestAttributes = new ArrayList(mExcludedRequestAttributes);
    }
    else
    {
      // Otherwise create an empty list
      mExcludedRequestAttributes = new ArrayList(5);
    }

    // Read excludedAttributes that may be defined in any face-config.xml
    readExcludedAttributesFromFacesConfig(portletContext, mExcludedRequestAttributes);

    // Set up the synchronziation object for the RequestScopeMap as we don't
    // want to sync on the PortletContext because its too broad. Note:
    // needed
    // because we not only need to sync the Map but also creating the Map
    // and
    // putting it in the PortletContext. Hence the sync object allows us
    // to limit syncronizing the PortletContext to once per portlet (init
    // time);

    // TODO: What about synching on a static object or using a class lock?
    //       Perhaps even the LRUMap itself if said map is a singleton?
    synchronized (portletContext)
    {
      Object lock = portletContext.getAttribute(REQUEST_SCOPE_LOCK);
      if (lock == null)
      {
        portletContext.setAttribute(REQUEST_SCOPE_LOCK, new Object());
      }
    }
   
    // Wrapped desired Application with our own to override createComponent and
    // insert our NamingContainerUIViewRoot component.  This was done through
    // configuration via the META-INF/service/javax.faces.application.ApplicationFactory
   
    // Add self as ELContextListener to the Faces App so we can add the
    // portletConfig to any newly created contexts.   
    ((ApplicationFactory)FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY))
        .getApplication().addELContextListener(this);


    // Process and cache the FacesServlet mappings for use by
    // ExternalContext
    WebConfigurationProcessor webConfig = new WebConfigurationProcessor(portletContext);
    mFacesMappings = webConfig.getFacesMappings();
    if (mFacesMappings == null || mFacesMappings.size() == 0)
    {
      throw new BridgeException("BridgeImpl.init(): unable to determine Faces servlet web.xml mapping.");
    }

    // Set defaults for each mode's last active view session attribute
    mDefaultViewIdMap = (Map<String,String>) portletContext.getAttribute(
                                Bridge.BRIDGE_PACKAGE_PREFIX + mPortletConfig.getPortletName()
                                      + "." + Bridge.DEFAULT_VIEWID_MAP);

    // remember that init() has been called so can test in other methods.
    mInitialized = true;

  }

  public void doFacesRequest(ActionRequest request, ActionResponse response)
    throws BridgeException,
           BridgeDefaultViewNotSpecifiedException,
           BridgeUninitializedException,
           NullPointerException
  {
    if (!mInitialized)
      throw new BridgeUninitializedException();
    else if (request == null || response == null)
      throw new NullPointerException("request or response parameter is null");

    // First check to see whether this is a non-JSF request
    if (request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER) != null)
    {
      throw new BridgeNotAFacesRequestException("NonFaces target = "
            + request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER));
    }

    // must wait until after init to get at the session
    // since view mode mapping must always exist -- check it
    StringBuffer keyBuf = new StringBuffer(30);
    String key = keyBuf.append(Bridge.VIEWID_HISTORY).append(".view").toString();
    if (request.getPortletSession().getAttribute(key) == null)
    {
      initViewHistoryDefaults(request.getPortletSession(), mDefaultViewIdMap);
    }

    // Set the Portlet lifecycle phase as a request attribute so its
    // available to Faces extensions -- allowing that code to NOT rely on
    // instanceof which can fail if a portlet container uses a single class
    // to implement both the action and render request/response objects
    request.setAttribute(Bridge.PORTLET_LIFECYCLE_PHASE, Bridge.PortletPhase.ACTION_PHASE);
   
    // Set the PortletName for use throughout this request to read portlet specific context attrs
    request.setAttribute(PORTLET_NAME_ATTRIBUTE, mPortletConfig.getPortletName());

    // Set the FacesServletMapping attribute so the ExternalContext can
    // pick it up and use it to reverse map viewIds to paths
    if (mFacesMappings != null)
    {
      request.setAttribute(PortletExternalContextImpl.FACES_MAPPING_ATTRIBUTE, mFacesMappings);
    }

    // cache names of existing request attributes so can exclude them
    // from being saved in the bridge's request scope. Note: this is done
    // before
    // acquiring the FacesContext because its possible (though unlikely)
    // the application has inserted itself in this process and sets up
    // needed request attributes.
    List<String> preExistingAttributes = getRequestAttributes(request);

    FacesContext context = null;
    Lifecycle lifecycle = null;
    String scopeId = null;
    try
    {
      // Get the FacesContext instance for this request
      lifecycle = getLifecycle();
      context = getFacesContext(request, response, lifecycle, null);
  
      // in case a prior scope was managed temporarily on the session -- remove it
      request.getPortletSession().removeAttribute(BRIDGE_PACKAGE_PREFIX + REQUEST_SCOPE_ID_RENDER_PARAM);
     
      // For actions we only execute the lifecycle phase

      lifecycle.addPhaseListener(this);
     
      lifecycle.execute(context);

      // If responseComplete don't save any state as we aren't falling through to render
      // Usual occurs because of a redirect
      if (!context.getResponseComplete())
      {
        // navigation didn't redirect
       
        // Finalize the action response -- key here is the reliance on
        // ExternalContext.encodeActionURL to migrate info encoded
        // in the actionURL constructed from the target of this
        // navigation
        // into the ActionResponse so it can be decoded in the
        // asscociated portlet render.

        finalizeActionResponse(context);
       
        // Process any Public Render parameter changes
        processOutgoingPublicRenderParameters(context, request, response);
       
       
        // Now check to see if we need to save the scope
        // We don't save scope if the finalizeActionResponse detected
        // a mode change
        Boolean noScope = (Boolean) request.getAttribute(PortletExternalContextImpl.NO_SCOPE);
        if (noScope == null || noScope.equals(Boolean.FALSE))
        {
       
          // Each action starts a new "action lifecycle"
          // The Bridge preserves request scoped data and if so configured
          // Action Parameters for the duration of an action lifecycle
          scopeId = initBridgeRequestScope(request, response);

          // Before preserving the request scope data in the bridge's
          // request scope,
          // put the Faces view into request scope. This is done because
          // JSF 1.2 manages the tree save state opaquely exclusively in
          // the render phase -- I.e. there is no JSF 1.2 way of having
          // the
          // bridge manually save and restore the view
          saveFacesView(context);

          // Spec requires we preserve the FACES_VIEW_STATE parameter
          // in addition the portlet may be configured to preserve the
          // rest of them.
          saveActionParams(context);

          // Because the portlet model doesn't execute its render phase
          // within the same request scope but Faces does (assumes this),
          // preserve the request scope data and the Faces view tree at
          // RequestScope.
          saveBridgeRequestScopeData(context, scopeId, preExistingAttributes);
        }

      }

    }
    catch (Exception e)
    {
      mPortletConfig.getPortletContext().log("Exception thrown in doFacesRequest:action", e);
      if (!(e instanceof BridgeException))
      {
        e =  new BridgeException(e);
      }
      throw (BridgeException) e;
    }
    finally
    {
      dumpScopeId(scopeId, "ACTION_PHASE");
     
      if (lifecycle != null)
      {
        lifecycle.removePhaseListener(this);
      }

      if (context != null)
      {
        // remove the redirect attr so its not carried over to the
        // render in environments in which action/render are run in the
        // same request ctx.  There are two potential attrs -- the one on
        // the request holds info if a redirect occurred within this request
        // The one on the session caches the redirect that occurred during render
        // so it can be used in subsequent renders that occur before the action.
        context.getExternalContext().getRequestMap().remove(BridgeImpl.REDIRECT_VIEWPARAMS);
        context.getExternalContext().getSessionMap().remove(BridgeImpl.RENDER_REDIRECT_VIEWPARAMS);
        context.getExternalContext().getSessionMap().remove(BridgeImpl.RENDER_REDIRECT_PRPMAP);

        context.release();
      }
     
      // our servletrequestattributelistener uses this as an indicator of whether
      // its actively working on a request -- remove it to indicate we are done
      request.removeAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);
    }
  }

  private void dumpScopeId(String scopeId, String phase)
  {
    // Get the data from the scope
    PortletContext ctx = mPortletConfig.getPortletContext();
    ctx.log("dumpScopeId: " + phase);
    synchronized (ctx.getAttribute(REQUEST_SCOPE_LOCK))
    {
      // get the managedScopeMap
      LRUMap requestScopeMap = (LRUMap) ctx.getAttribute(REQUEST_SCOPE_MAP);
      // No scope for all renders before first action to this portletApp
      if (requestScopeMap == null)
      {
        ctx.log("There are No saved scoped.  Can't match: " + scopeId);
        return;
      }

      Map<String, Object> m = requestScopeMap.get(scopeId);
      if (m == null)
      {
        ctx.log("Can't match scope: " + scopeId);
        return;
      }

      Set<Map.Entry<String, Object>> set = m.entrySet();
      Iterator<Map.Entry<String, Object>> i = set.iterator();
      ctx.log("Elements in scope: " + scopeId);
      while (i.hasNext())
      {
        Map.Entry<String, Object> entry = i.next();
        ctx.log("     " + entry.getKey());
      }
      ctx.log("end dumpScopeId");
    }

  }
 
  public void doFacesRequest(EventRequest request, EventResponse response)
    throws BridgeException,
           BridgeDefaultViewNotSpecifiedException,
           BridgeUninitializedException,
           NullPointerException
  {
    if (!mInitialized)
      throw new BridgeUninitializedException();
    else if (request == null || response == null)
      throw new NullPointerException("request or response parameter is null");
   
    // Do nothing if we don't have a BriegeEventHandler
    if (mEventHandler == null)
    {
      // make sure the render parameters are carried forward into the next request
      response.setRenderParameters(request.getParameterMap());
      // TODO: maybe log something here?
      return;
    }

    // First check to see whether this is a non-JSF request
    if (request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER) != null)
    {
      throw new BridgeNotAFacesRequestException("NonFaces target = "
            + request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER));
    }

    // must wait until after init to get at the session
    // since view mode mapping must always exist -- check it
    StringBuffer keyBuf = new StringBuffer(30);
    String key = keyBuf.append(Bridge.VIEWID_HISTORY).append(".view").toString();
    if (request.getPortletSession().getAttribute(key) == null)
    {
      initViewHistoryDefaults(request.getPortletSession(), mDefaultViewIdMap);
    }

    // Set the Portlet lifecycle phase as a request attribute so its
    // available to Faces extensions -- allowing that code to NOT rely on
    // instanceof which can fail if a portlet container uses a single class
    // to implement both the action and render request/response objects
    request.setAttribute(Bridge.PORTLET_LIFECYCLE_PHASE, Bridge.PortletPhase.EVENT_PHASE);
   
    // Set the PortletName for use throughout this request to read portlet specific context attrs
    request.setAttribute(PORTLET_NAME_ATTRIBUTE, mPortletConfig.getPortletName());

    // Set the FacesServletMapping attribute so the ExternalContext can
    // pick it up and use it to reverse map viewIds to paths
    if (mFacesMappings != null)
    {
      request.setAttribute(PortletExternalContextImpl.FACES_MAPPING_ATTRIBUTE, mFacesMappings);
    }

    // cache names of existing request attributes so can exclude them
    // from being saved in the bridge's request scope. Note: this is done
    // before
    // acquiring the FacesContext because its possible (though unlikely)
    // the application has inserted itself in this process and sets up
    // needed request attributes.
    List<String> preExistingAttributes = getRequestAttributes(request);
   
    // Make sure that at a minimum the current render parameters are carried forward
    response.setRenderParameters(request.getParameterMap());

    FacesContext context = null;
    Lifecycle lifecycle = null;
    String scopeId = getRequestScopeId(request);
    if (scopeId != null)
    {
      // Its possible we didn't detect the mode change but its the wrong scope
      // as the scope is encoded with the mode -- confirm its right
      StringBuffer sb = new StringBuffer(10);
      String modeCheck = sb.append(":").append(request.getPortletMode().toString()).append(":").toString();
      if (scopeId.indexOf(modeCheck) < 0 )
      {
        // scope is for a different mode
        scopeId = null;
      }
    }
    boolean restoredScope = false;
   
    restoredScope = restoreBridgeRequestScopeData(request, scopeId);
   
    try
    {
      lifecycle = getLifecycle();
      // Get the FacesContext instance for this request
      context = getFacesContext(request, response, lifecycle, null);
     
      if (restoredScope)
      {
        // only restores if a render has never occurred in this scope
        // once a render occurs, the view is saved/restored via Faces
        restoreFacesView(context, scopeId);       
      }
  
      // in case a prior scope was managed temporarily on the session -- remove it
      // also ensure that the scopeId is now carried forward as a renderParameter
      request.getPortletSession().removeAttribute(BRIDGE_PACKAGE_PREFIX + REQUEST_SCOPE_ID_RENDER_PARAM);
      if (scopeId != null)
      {
        response.setRenderParameter(REQUEST_SCOPE_ID_RENDER_PARAM, scopeId);
      }
     
      // add self as PhaseListener to prevent action phase from executing (after restoreView)
      lifecycle.addPhaseListener(this);

      // For actions we only execute the lifecycle phase
      lifecycle.execute(context);
     
      // call the eventhandler to process
      EventNavigationResult result = mEventHandler.handleEvent(context, request.getEvent());
     
      // If redirected either during lifecycle or event handling merely return as new target is already encoded in response.
      if (context.getResponseComplete()) return;
     
      if (result != null)
      {
        context.getApplication().getNavigationHandler().handleNavigation(context, result.getFromAction(), result.getOutcome());
      }

      finalizeActionResponse(context);
       
      // Process any Public Render parameter changes
      processOutgoingPublicRenderParameters(context, request, response);
       
       
      // Now check to see if we need to save the scope
      // We don't save scope if the finalizeActionResponse detected
      // a mode change
      Boolean noScope = (Boolean) request.getAttribute(PortletExternalContextImpl.NO_SCOPE);
      if (noScope == null || noScope.equals(Boolean.FALSE))
      {
       
        // If event occurred before an action we don't have acope yet
        if (scopeId == null)
        {
          scopeId = initBridgeRequestScope(request, response);
         
          // TBD: Mojarra 1.2_05 or later sets responseComplete if the restore occurs in
          // a request that doesn't contain the isPostback marker (VIEW_STATE_PARAM).
          // If we have no scopeId it means we received an event before the portlet
          // was asked to render.  In these versions the following render call will
          // fail to render because there is no VIEW_STATE_PARAM.  Should we artifucally
          // set this here?
        }

        // Before preserving the request scope data in the bridge's
        // request scope,
        // put the Faces view into request scope. This is done because
        // JSF 1.2 manages the tree save state opaquely exclusively in
        // the render phase -- I.e. there is no JSF 1.2 way of having
        // the
        // bridge manually save and restore the view
        saveFacesView(context);

        // Because the portlet model doesn't execute its render phase
        // within the same request scope but Faces does (assumes this),
        // preserve the request scope data and the Faces view tree at
        // RequestScope.
        saveBridgeRequestScopeData(context, scopeId, preExistingAttributes);
      }

    }
    catch (Exception e)
    {
      mPortletConfig.getPortletContext().log("Exception thrown in doFacesRequest:event", e);
      if (!(e instanceof BridgeException))
      {
        e = new BridgeException(e);
      }
      throw (BridgeException) e;
    }
    finally
    {
      dumpScopeId(scopeId, "EVENT_PHASE");
      if (lifecycle != null)
      {
        lifecycle.removePhaseListener(this);
      }

      if (context != null)
      {
        // remove the redirect attr so its not carried over to the
        // render in environments in which action/render are run in the
        // same request ctx.  There are two potential attrs -- the one on
        // the request holds info if a redirect occurred within this request
        // The one on the session caches the redirect that occurred during render
        // so it can be used in subsequent renders that occur before the action.
        context.getExternalContext().getRequestMap().remove(BridgeImpl.REDIRECT_VIEWPARAMS);
        context.getExternalContext().getSessionMap().remove(BridgeImpl.RENDER_REDIRECT_VIEWPARAMS);
        context.getExternalContext().getSessionMap().remove(BridgeImpl.RENDER_REDIRECT_PRPMAP);
        context.release();
      }
     
      // our servletrequestattributelistener uses this as an indicator of whether
      // its actively working on a request -- remove it to indicate we are done
      request.removeAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);
    }
  }


  public void doFacesRequest(RenderRequest request, RenderResponse response)
    throws BridgeException,
           BridgeDefaultViewNotSpecifiedException,
           BridgeUninitializedException,
           NullPointerException
  {
    if (!mInitialized)
      throw new BridgeUninitializedException();
    else if (request == null || response == null)
      throw new NullPointerException("request or response parameter is null");

    // First check to see whether this is a non-JSF render request
    if (request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER) != null)
    {
      throw new BridgeNotAFacesRequestException("NonFaces target = "
            + request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER));
    }
   
    // must wait until after init to get at the session
    // since view mode mapping must always exist -- check it
    StringBuffer keyBuf = new StringBuffer(30);
    String key = keyBuf.append(Bridge.VIEWID_HISTORY).append(".view").toString();
    if (request.getPortletSession().getAttribute(key) == null)
    {
      initViewHistoryDefaults(request.getPortletSession(), mDefaultViewIdMap);
    }
   
    String scopeId = null;
    boolean restoredScope = false;
    boolean restoredView = false;

    // Set the Portlet lifecycle phase as a request attribute so its
    // available to Faces extensions -- allowing that code to NOT rely on
    // instanceof which can fail if a portlet container uses a single class
    // to implement both the action and render request/response objects
    request.setAttribute(Bridge.PORTLET_LIFECYCLE_PHASE, Bridge.PortletPhase.RENDER_PHASE);

    // Set the PortletName for use throughout this request to read portlet specific context attrs
    request.setAttribute(PORTLET_NAME_ATTRIBUTE, mPortletConfig.getPortletName());
   
    // Set the WriteBehindResponse the ViewHandler should use
    if (mBridgeWriteBehindResponse != null)
    {
      request.setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + Bridge.WRITE_BEHIND_RESPONSE, mBridgeWriteBehindResponse);
    }
   
    // Set the FacesServletMapping attribute so the ExternalContext can
    // pick it up and use it to reverse map viewIds to paths
    if (mFacesMappings != null)
    {
      request.setAttribute(PortletExternalContextImpl.FACES_MAPPING_ATTRIBUTE, mFacesMappings);
    }

    // cache existing attributes in case a redirect occurs and
    // we need to remove all but these preexisting ones.
    List<String> preExistingAttributes = getRequestAttributes(request);
   
    // Now we need to determine if view we are going to render is the one
    // encoded in the request (parameters) or specifically supplied
    // either by the portlet itself or by a prior render redirect.
    boolean clientDirectedView = ((request.getAttribute(Bridge.VIEW_ID) != null) ||
        (request.getAttribute(Bridge.VIEW_PATH) != null));

    // Now check to see if this is a Refresh (render) that follows a redirect
    // If it is use the redirect information cached in the session as the basis
    // for the request.
    QueryString redirectParams = (QueryString)
              request.getPortletSession(true).getAttribute(BridgeImpl.RENDER_REDIRECT_VIEWPARAMS);
   
    boolean modeChanged = hasModeChanged(request, redirectParams);

    if (redirectParams != null)
    {
      if (clientDirectedView || modeChanged
      {
        // if we are too rely on the render redirect cache we must still
        // be in the same mode and/or the portlet can't have set an explicit view to use
       
        // mode change/direct view set while in redirectDuringRender state
        // clear this state (as we are no longer in it)
        // and don't use the data
        redirectParams = null;
        request.getPortletSession().removeAttribute(BridgeImpl.RENDER_REDIRECT_VIEWPARAMS);
        request.getPortletSession().removeAttribute(BridgeImpl.RENDER_REDIRECT_PRPMAP);
      }
      else
      {
        // Incoming request may contain new PRP values -- if they do we need to update the RENDER_REDIRECT_VIEWPARAMS
        updatePublicRedirectParams(request, redirectParams, (Map<String, String[]>)request.getPortletSession(true).getAttribute(BridgeImpl.RENDER_REDIRECT_PRPMAP));
      }
    }
   
   
    // never restore a scope if relying on render redirect cache
    if (redirectParams == null && !clientDirectedView)
    {
      // If available -- restore the bridge request scope before getting the
      // FacesContext in case anything in the context construction relies
      // on these restored values.
      // don't restore scope if mode changed
      scopeId = getRequestScopeId(request);
    }
   
    if (scopeId != null)
    {
      restoredScope = restoreBridgeRequestScopeData(request, scopeId);
    }
    else
    {
      // first request
      // create a scope and store in the session until an action occurs
      // pass null as we aren't a StateAwareResponse
      scopeId = initBridgeRequestScope(request, null);
    }
   

    FacesContext context = null;
    try
    {
      // Get the FacesContext instance for this request
      Lifecycle lifecycle = getLifecycle();
      context = getFacesContext(request, response, lifecycle, redirectParams);
      ExternalContext extCtx = context.getExternalContext();

      // Use request from ExternalContext in case its been wrapped by an
      // extension
      RenderRequest extRequest = (RenderRequest) extCtx.getRequest();

      if (restoredScope)
      {
        // Because the Bridge is required to always save/restore the
        // VIEW_STATE
        // parameter -- always attempt a restore
        extRequest = restoreActionParams(context);
       
        // only restores if first render after action
        // afterwards not restored from Bridge request scope
        // rather its saved/restored by Faces.
        restoredView = restoreFacesView(context, scopeId);
      }

      // ensure that isPostback attribute set if VIEW_STATE param exists (though Faces generally
      // uses the VIEW_STATE param as indication of whether its a postback or not -- this is a
      // bridge spec defined flag that can also be used.
      if (extCtx.getRequestParameterValuesMap().containsKey(ResponseStateManager.VIEW_STATE_PARAM))
      {
        extCtx.getRequestMap().put(Bridge.IS_POSTBACK_ATTRIBUTE, Boolean.TRUE);
      }

      doFacesRender(request, response, context, lifecycle, scopeId, preExistingAttributes);
      // get contexts as they may have been reacquired if a redirect during render occurred.
      context = FacesContext.getCurrentInstance();
      extCtx = context.getExternalContext();
    }
    catch (Exception e)
    {
      // get context as it may have been reacquired if exception occurred during redirect during render.
      context = FacesContext.getCurrentInstance();

      mPortletConfig.getPortletContext().log("Exception thrown in doFacesRequest:render", e);
      if (!(e instanceof BridgeException))
      {
        e = new BridgeException(e);
      }
      throw (BridgeException) e;
    }
    finally
    {
      dumpScopeId(scopeId, "RENDER_PHASE");

      if (context != null)
      {
        context.release();
      }
     
      // our servletrequestattributelistener uses this as an indicator of whether
      // its actively working on a request -- remove it to indicate we are done
      request.removeAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);
    }
  }


  private boolean hasModeChanged(RenderRequest request, QueryString renderRedirectParams)
  {
    boolean hasModeChanged = false;
    String targetViewId = null;
    // if renderRedirectParams != null then this is a refresh following a render redirect
    // In this case pull the scopeId from the redirectParams rather tahn the request
    // as we will wrap the request (later) to expose these params to Faces
    if (renderRedirectParams == null)
    {
      targetViewId =  request.getParameter(PortletExternalContextImpl.JSF_TARGET_VIEWID_RENDER_PARAMETER);
    }
    else
    {
      targetViewId = renderRedirectParams.getParameter(PortletExternalContextImpl.JSF_TARGET_VIEWID_RENDER_PARAMETER);
    }
   
    if (targetViewId != null)
    {
      int i = targetViewId.indexOf(':');
      if (i >= 0 )
      {
     
        String mode = targetViewId.substring(0, i);
        if (!mode.equalsIgnoreCase(request.getPortletMode().toString()))
        {
          hasModeChanged = true;
        }
      }
    }
   
    return hasModeChanged;
  }
 
  private void doFacesRender(
      PortletRequest request,
      MimeResponse response,
      FacesContext context,
      Lifecycle lifecycle,
      String scopeId,
      List<String> preExistingAttributes
      )
    throws BridgeException, BridgeUninitializedException, NullPointerException
  {
    boolean redirectedDuringRender = false;

    // Note: if the scope wasn't restored then the Faces
    // FACES_VIEW_STATE
    // parameter will not have been carried into this render and hence
    // default Faces impls will not see this render as occuring in a
    // in a postback (isPostback() will return false. This means Faces
    // will create a new Tree instead of restoring one -- the semantics
    // one should get if the Bridge can't access its requestScope.
     
    try
    {
      // add self as PhaseListener to restore Messages and in the case of
      // the render phase, prevent excuting beyond restoreView
      lifecycle.addPhaseListener(this);
     
      lifecycle.execute(context);
    }
    catch (Exception e)
    {    
      // now rethrow the exception as a BridgeException
      if (!(e instanceof BridgeException))
      {
        e =  new BridgeException(e);
      }
      throw (BridgeException) e;
    }
    finally
    {
      lifecycle.removePhaseListener(this);
    }

    // check here to see if a redirect occurred -- if so rerun doFacesRequest
    // for this new view
    QueryString redirectParams = (QueryString) context.getExternalContext()
            .getRequestMap().get(BridgeImpl.REDIRECT_VIEWPARAMS);
   
    if (redirectParams == null)
    {
      // In resource case and/or redirect overriden responseComplete
      // May have been set -- so don't ignore it
      if (!context.getResponseComplete())
      {
        lifecycle.render(context);
      }
    }
    else
    {
      redirectRender(context,
        lifecycle, request, response,
        redirectParams, preExistingAttributes);
     
      context = FacesContext.getCurrentInstance();
      redirectedDuringRender = true;
    }


    // check here to see if a redirect occurred -- if so rerun doFacesRequest
    // for this new view
    // Can't use responseComplete because normal lifecycle.render completion
    // sets this.
    redirectParams = (QueryString) context.getExternalContext()
            .getRequestMap().get(BridgeImpl.REDIRECT_VIEWPARAMS);
    if ((redirectParams != null))
    {
      redirectRender(context,
        lifecycle, request, response,
        redirectParams, preExistingAttributes);
      context = FacesContext.getCurrentInstance();
      redirectedDuringRender = true;
    }

    // At a minimum we need to update the VIEW_STATE_PARAM being maintained
    // However, if this is a resource request then we also update the full scope
    if (scopeId != null)
    {
      // Don't update if we have redirected during this render instead remove the scope
      if (!redirectedDuringRender)
      {
        saveFacesMessageState(context);
        saveBridgeRequestScopeData(context, scopeId, preExistingAttributes);
        updateViewInfo(context, scopeId);
      }
      else
      {
        removeRequestScopes(scopeId);
      }
    }
  }

  public void doFacesRequest(ResourceRequest request, ResourceResponse response)
    throws BridgeException,
           BridgeDefaultViewNotSpecifiedException,
           BridgeUninitializedException,
           NullPointerException
  {
    if (!mInitialized)
      throw new BridgeUninitializedException();
    else if (request == null || response == null)
      throw new NullPointerException("request or response parameter is null");

    // Set the Portlet lifecycle phase as a request attribute so its
    // available to Faces extensions -- allowing that code to NOT rely on
    // instanceof which can fail if a portlet container uses a single class
    // to implement both the action and render request/response objects
    request.setAttribute(Bridge.PORTLET_LIFECYCLE_PHASE, Bridge.PortletPhase.RESOURCE_PHASE);

    // Determine whether this is a Faces resource or a regular one.
    if (request.getParameter(PortletExternalContextImpl.JSF_RESOURCE_TARGET_VIEWID_RENDER_PARAMETER) == null)
    {
      try
      {
        String target = request.getResourceID();
        PortletRequestDispatcher rd =
          mPortletConfig.getPortletContext().getRequestDispatcher(target);
        rd.forward(request, response);
        return;
      }
      catch (Exception e)
      {
        if (!(e instanceof BridgeException))
        {
          e = new BridgeException(e);
        }
        throw (BridgeException) e;
      }
    }

    // Otherwise this is a Faces resource
   
    // must wait until after init to get at the session
    // since view mode mapping must always exist -- check it
    StringBuffer keyBuf = new StringBuffer(30);
    String key = keyBuf.append(Bridge.VIEWID_HISTORY).append(".view").toString();
    if (request.getPortletSession().getAttribute(key) == null)
    {
      initViewHistoryDefaults(request.getPortletSession(), mDefaultViewIdMap);
    }
   

    // Set the PortletName for use throughout this request to read portlet specific context attrs
    request.setAttribute(PORTLET_NAME_ATTRIBUTE, mPortletConfig.getPortletName());
   
    // Set the WriteBehindResponse the ViewHandler should use
    if (mBridgeWriteBehindResponse != null)
    {
      request.setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + Bridge.WRITE_BEHIND_RESPONSE, mBridgeWriteBehindResponse);
    }


    // Set the FacesServletMapping attribute so the ExternalContext can
    // pick it up and use it to reverse map viewIds to paths
    if (mFacesMappings != null)
    {
      request.setAttribute(PortletExternalContextImpl.FACES_MAPPING_ATTRIBUTE, mFacesMappings);
    }

    // cache names of existing request attributes so can exclude them
    // from being saved in the bridge's request scope. Note: this is done
    // before
    // acquiring the FacesContext because its possible (though unlikely)
    // the application has inserted itself in this process and sets up
    // needed request attributes.
    List<String> preExistingAttributes = getRequestAttributes(request);

    String scopeId = getRequestScopeId(request);
    if (scopeId == null)
    {
      // first request is a resource request
      // create a scope and store in the session until an action occurs
      // pass null as we aren't a StateAwareResponse
      scopeId = initBridgeRequestScope(request, null);
    }

    FacesContext context = null;
    try
    {
      // Get the FacesContext instance for this request
      Lifecycle lifecycle = getLifecycle();
      context = getFacesContext(request, response, lifecycle, null);
      ExternalContext extCtx = context.getExternalContext();

      // Use request from ExternalContext in case its been wrapped by an
      // extension
      ResourceRequest extRequest = (ResourceRequest) extCtx.getRequest();

      // ensure that isPostback attribute set if VIEW_STATE param exists
      if (extCtx.getRequestParameterValuesMap().containsKey(ResponseStateManager.VIEW_STATE_PARAM))
      {
        extCtx.getRequestMap().put(Bridge.IS_POSTBACK_ATTRIBUTE, Boolean.TRUE);
      }

      doFacesRender(request, response, context, lifecycle, scopeId, preExistingAttributes);
    }
    catch (Exception e)
    {
      context.getExternalContext().log("Exception thrown in doFacesRequest:resource", e);
      if (!(e instanceof BridgeException))
      {
        e = new BridgeException(e);
      }
      throw (BridgeException) e;
    }
    finally
    {
      dumpScopeId(scopeId, "RENDER_PHASE");

      if (context != null)
      {
        context.release();
      }
     
      // our servletrequestattributelistener uses this as an indicator of whether
      // its actively working on a request -- remove it to indicate we are done
      request.removeAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);
    }
  }


  public void destroy()
  {
    if (!mInitialized)
      // do nothing if destroy an uninitialzed bridge
      return;

    mInitialized = false;
   
    // Remove the context listener
    ((ApplicationFactory)FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY))
        .getApplication().removeELContextListener(this);

    // remove any scopes being managed for this portlet
    // Each scope has a per portlet prefix -- pass in the prefix
    // constructed by adding the prefix to an empty string.
    removeRequestScopes(qualifyScopeId(mPortletConfig.getPortletName(), null, null, null));

    mPortletConfig = null;
  }
 
  /**
   * ELContextListener impl
   */
 
  public void contextCreated(ELContextEvent ece)
  {
    // Add the portletConfig to the ELContext so it is evaluated
    ELContext elContext = ece.getELContext();

    // FacesContext (where the Faces/Bridge ELContext is created doesn't have
    // access to the PortletConfig which the Bridge ELResolver needs.
    // The config object is added as an attribute here in the ContextListener.
    // However only add to a EL context created within Faces as the JSP
    // ELContext/Resolver will naturally resolve the config (as long as the
    // page devleper has used the <portlet:defineObjects> tag.
    // Because listeners are called at app scope we must ensure that only
    // the active portlet's config is added to the ELContext.  To do this, check
    // the portletName previously stored as a request attribute against the config.
   
    // Make sure our bridge instance is handling this context
    String portletName = (String) ((FacesContext) elContext.getContext(FacesContext.class)).getExternalContext().getRequestMap().get(PORTLET_NAME_ATTRIBUTE);
    if (portletName != null && portletName.equals(mPortletConfig.getPortletName()))
    {
      PortletELContextImpl portletELContext;
      if (elContext instanceof PortletELContextImpl)
      {
        // Grr -- turns out that by the time my resolver is called the ELContext may
        // have been wrapped -- so mark here as a FacesResolver and then do a put context
        portletELContext = (PortletELContextImpl) elContext;
        portletELContext.setFacesResolved(true);
        // Put the portletConfig object into this Map
        portletELContext.setPortletConfig(mPortletConfig);

      }
      else
      {
        // create a PortletELContext to hold future resolver state and place on this context
        portletELContext = new PortletELContextImpl(elContext.getELResolver());
        portletELContext.setFacesResolved(false);
      }
      elContext.putContext(PortletELContextImpl.class, portletELContext);
    }
  }

  /*
   * ServletRequestAttributeListener implementation
   */
  public void attributeAdded(ServletRequestAttributeEvent srae)
  {
    // use this phase attribute as an indicator of whether
    // we are actively working on a request
    PortletPhase phase = (PortletPhase) srae.getServletRequest().getAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);

    // do nothing if before/after bridge processing or in the render phase.
    // Don't care about render phase because we don't update/change the managed
    // scope based on changes during render.
    // ALSO: do nothing if not in the Bridge's managed request scope
    if (phase == null || phase == PortletPhase.RENDER_PHASE ||
        isExcludedFromBridgeRequestScope(srae.getName(),
                                         srae.getValue(),
                                         (List<String>) srae.getServletRequest()
                                            .getAttribute(PREEXISTING_ATTRIBUTE_NAMES)))
    {
      return;
    }

    // Otherwise -- see if the added attribute implements the bridge's
    // BridgeRequestScopeAdded annotation -- call each method so annotated
    Object o = srae.getValue();
    Method[] methods = o.getClass().getMethods();
    for (int i = 0; i < methods.length; i++)
    {
      if (methods[i].isAnnotationPresent(BridgeRequestScopeAttributeAdded.class))
      {
        try
        {
          methods[i].invoke(o, null);
        }
        catch (Exception e)
        {
          // TODO: log problem
          // do nothing and forge ahead
          ;
        }
      }
    }
  }

  public void attributeRemoved(ServletRequestAttributeEvent srae)
  {
    // use this phase attribute as an indicator of whether
    // we are actively working on a request
    PortletPhase phase = (PortletPhase) srae.getServletRequest().getAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);

    // If not in a render this means the attribute has been removed before we have
    // saved the request scope -- since the managed bean has been informed we are
    // running in a portlet environment it should have ignored the PreDestroy.
    // To make up for this we call its BridgePredestroy
    if (phase != null && phase != PortletPhase.RENDER_PHASE)
    {
      notifyPreDestroy(srae.getValue()); // in outerclass (BridgeImpl)
    }
  }

  public void attributeReplaced(ServletRequestAttributeEvent srae)
  {
    // use this phase attribute as an indicator of whether
    // we are actively working on a request
    PortletPhase phase = (PortletPhase) srae.getServletRequest().getAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);

    // If in an action this means the attribute has been replaced before we have
    // saved the action scope -- since the managed bean has been informed we are
    // running in a portlet environment it should have ignored the PreDestroy.
    // To make up for this we call its BridgePredestroy
    if (phase != null && phase != PortletPhase.RENDER_PHASE)
    {
      notifyPreDestroy(srae.getValue()); // in outerclass (BridgeImpl)
    }
  }

  private void initViewHistoryDefaults(PortletSession session, Map<String, String> viewIdDefaultMap)
  {
    Set<String> keys = viewIdDefaultMap.keySet();
    Iterator<String> i = keys.iterator();
   
    while (i.hasNext())
    {
      String mode = i.next();
      String modeView = viewIdDefaultMap.get(mode);
     
      if (modeView == null || modeView.length() == 0) continue;
     
      String defaultViewId = encodeMode(mode, modeView);
      StringBuffer key = new StringBuffer(100);
      session.setAttribute(
          key.append(Bridge.VIEWID_HISTORY).append('.').append(mode).toString(),
          defaultViewId);
    }
  }
 
  private String encodeMode(String mode, String viewId)
  {
    // if the viewId doesn't otherwise contain a mode param, encode it
    // this is done so its already encoded in the viewid when developer uses
    // to return to the mode/view
    StringBuffer sb = new StringBuffer(viewId.length() + 30);
    int queryStart = viewId.indexOf('?');
    if (queryStart < 0)
    {
      return sb.append(viewId)
        .append('?')
        .append(Bridge.PORTLET_MODE_PARAMETER)
        .append('=')
        .append(mode)
        .toString();
    }
    else
    {
      QueryString qs = new QueryString(viewId.substring(queryStart + 1), "UTF8");
      qs.setParameter(Bridge.PORTLET_MODE_PARAMETER, mode);
      return sb.append(viewId.substring(0, queryStart + 1)).append(qs.toString()).toString();
    }
  }
 
  @SuppressWarnings( "unchecked" )
  private ArrayList<String> getPublicParameterNamesList()
  {
    Enumeration<String> e = mPortletConfig.getPublicRenderParameterNames();
    ArrayList<String>  l = new ArrayList<String>();
    if (!e.hasMoreElements()) return l;
     
    while (e.hasMoreElements())
    {
      l.add(e.nextElement());
    }
    // sort so we can use binary search to compare
    Collections.sort(l);
    return l;
  }

  private RenderRequest wrapRequestToRedirect(RenderRequest request,
                              QueryString redirectParams)
  {
    // remove any bridge special marker params as these can't be set in a
    // render redirect
    redirectParams.removeParameter(Bridge.DIRECT_LINK);
    redirectParams.removeParameter(Bridge.PORTLET_MODE_PARAMETER);
    redirectParams.removeParameter(Bridge.PORTLET_WINDOWSTATE_PARAMETER);
    redirectParams.removeParameter(Bridge.PORTLET_SECURE_PARAMETER);

    // Now turn the QueryString into a parameter map
    Map<String, String[]> publicParamMap = new LinkedHashMap(redirectParams.numParameters());
    Map<String, String[]> privateParamMap = new LinkedHashMap(redirectParams.numParameters());
    ArrayList<String> publicParamNamesList = getPublicParameterNamesList();
    Enumeration<String> nameEnum = redirectParams.getParameterNames();
    while (nameEnum != null && nameEnum.hasMoreElements())
    {
      String name = nameEnum.nextElement();
      Enumeration<String> valuesEnum = redirectParams.getParameterValues(name);
      ArrayList<String> values = new ArrayList();
      while (valuesEnum != null && valuesEnum.hasMoreElements())
      {
        values.add(valuesEnum.nextElement());
      }
     
      if (Collections.binarySearch(publicParamNamesList, name) >= 0)
      {
        publicParamMap.put(name, values.toArray(new String[values.size()]));
      }
      else
      {
        privateParamMap.put(name, values.toArray(new String[values.size()]));
      }
    }

    // now wrap the request object to expose only those params as are in QS
    return (RenderRequest) new BridgeRenderRequestWrapper(request, privateParamMap, publicParamMap, false);
  }
 
  private void updatePublicRedirectParams(PortletRequest request, QueryString redirectParams, Map<String, String[]> cachedPRPs)
  {
    Map<String, String[]> prps = request.getPublicParameterMap();
    boolean modified = false;
   
    if (cachedPRPs == null)
    {
      cachedPRPs = Collections.EMPTY_MAP;
    }
   
    // For each cachedPRP check to see if the prp exists in the current request and has the same value
    // If any change -- remove/update in the QS
    for (Map.Entry<String, String[]> entry : cachedPRPs.entrySet())
    {
      String key = entry.getKey();
      if (prps.containsKey(key))
      {
        if (!Arrays.equals(prps.get(key), cachedPRPs.get(key)))
        {
          modified = true;
          redirectParams.removeParameter(key);
          for (String param : prps.get(key))
          {
            redirectParams.addParameter(key, param);
          }
        }
      }
      else
      {
        // Its in the cachedPRP but not the current request -- so its been cleared
        modified = true;
        redirectParams.removeParameter(key);
      }
    }
   
    // Now check the reverse -- add those that are in the current request but not the cache
    for (Map.Entry<String, String[]> entry : prps.entrySet())
    {
      String key = entry.getKey();
      if (!cachedPRPs.containsKey(key))
      {
        for (String param : prps.get(key))
        {
          modified = true;
          redirectParams.addParameter(key, param);
        }

      }
    }
   
    // Update the Map/redirectParams in the cache to the current values
    if (modified)
    {
      request.getPortletSession().setAttribute(BridgeImpl.RENDER_REDIRECT_VIEWPARAMS, redirectParams);
      request.getPortletSession().setAttribute(BridgeImpl.RENDER_REDIRECT_PRPMAP, new HashMap<String, String[]>(prps));
    }
  }

  private void redirectRender(FacesContext context,
                              Lifecycle lifecycle,
                              PortletRequest request,
                              MimeResponse response,
                              QueryString redirectParams,
                              List<String> preExistingAttrs)
  {
   
    if (context.getExternalContext().getSessionMap().get(BridgeImpl.RENDER_REDIRECT_PRPMAP) == null)
    {
      // Grab the current public parameter map so we can cache it so we can fix up render redisplays
      // where the incoming request changes a PRP. Do it before redirect to make sure we don't
      // perturb the result. This also ensures its done on the first redirect in this render -- which is what we want
      // as this is the original PRPs of this request (unchanged by any other redirect)

      context.getExternalContext().getSessionMap().put(BridgeImpl.RENDER_REDIRECT_PRPMAP,
                                                       new HashMap<String, String[]>(request.getPublicParameterMap()));
    }
   
    // The Bridge ViewHandler will set the HAS_RENDER_REDIRECTED_AFTER_FORWARD request attr if
    // the redirect occurs after the dispatch.forward -- be sure to carry this forward
    // into the new wrapped request so the redirect will use include
    Boolean hasRenderRedirectedAfterForward = (Boolean) request.getAttribute(HAS_RENDER_REDIRECTED_AFTER_FORWARD);
   
    // close the FacesContext
    context.release();
   
    // Now put the HAS_RENDER_REDIRECTED_AFTER_FORWARDflag back
    if (hasRenderRedirectedAfterForward != null)
    {
      request.setAttribute(HAS_RENDER_REDIRECTED_AFTER_FORWARD, hasRenderRedirectedAfterForward);
    }
   
    // This removed the cache of preexisting request attributes -- so acquire again
    getRequestAttributes(request);
      
    // start a new FacesContext
    context = getFacesContext(request, response, lifecycle, redirectParams);
   
    // Deal with possible recursion by clearing the cache, then calling doFacesRender
    // to render the redirect, and then finally setting the cache, if its hasn't
    // already been set.  In the (recursive) multi-render redirect case, the
    // last redirect will be the only one that sets the cache.
    context.getExternalContext().getSessionMap().remove(BridgeImpl.RENDER_REDIRECT_VIEWPARAMS);
   
    // Run lifecycle.execute again ... then render
    doFacesRender(request, response, context, lifecycle, null, preExistingAttrs);
   
    // Reacquire the context as it may have changed if a recursive redirect render occurred
    context = FacesContext.getCurrentInstance();
   
    // now cache the (last) redirect params on session for resuse in subsequent renders
    // that occur before an action
    if (context.getExternalContext().getSessionMap().get(BridgeImpl.RENDER_REDIRECT_VIEWPARAMS) == null)
    {
      context.getExternalContext().getSessionMap().put(BridgeImpl.RENDER_REDIRECT_VIEWPARAMS, redirectParams);
    }

  }

  private FacesContextFactory getFacesContextFactory()
    throws BridgeException
  {
    try
    {
      if (mFacesContextFactory == null)
      {
        mFacesContextFactory =
            (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
      }
      return mFacesContextFactory;
    }
    catch (FacesException e)
    {
      throw new BridgeException(e);
    }
  }
 
  private FacesContext getFacesContext(PortletRequest request, PortletResponse response, Lifecycle lifecycle, QueryString redirectParams)
    throws FacesException
  {
    FacesContext context = null;
   
    context =
          getFacesContextFactory().getFacesContext(mPortletConfig.getPortletContext(), request, response, lifecycle);
   
    if (redirectParams != null)
    {
      // render redirect -- wrap the request
     
      // call ExternalContext.setRequest with wrapped request
      context.getExternalContext().setRequest(wrapRequestToRedirect((RenderRequest)request, redirectParams));
    }
   
    return context;

  }

  private Lifecycle getLifecycle()
    throws BridgeException
  {
    try
    {
      if (mLifecycle == null)
      {
        LifecycleFactory lifecycleFactory =
          (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
        String lifecycleId =
          mPortletConfig.getPortletContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
        if (lifecycleId == null)
        {
          lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
        }

        mLifecycle = lifecycleFactory.getLifecycle(lifecycleId);
      }
      return mLifecycle;
    }
    catch (FacesException e)
    {
      throw new BridgeException(e);
    }
  }

  private void saveFacesView(FacesContext context)
  {

    // first save any current Faces messages in the viewRoot
    saveFacesMessageState(context);

    // now place the viewRoot in the request scope
    Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
    requestMap.put(FACES_VIEWROOT, context.getViewRoot());
  }
 
  private boolean restoreFacesView(FacesContext context, String scopeId)
  {
    Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
    UIViewRoot viewRoot = (UIViewRoot) requestMap.get(FACES_VIEWROOT);
    if (viewRoot != null)
    {
      context.setViewRoot(viewRoot);
      // remove from current Request Scope
      requestMap.remove(FACES_VIEWROOT);
     
      // Delay removing from the bridge request scope until after we are sure that
      // render completes okay and the state is saved by Faces -- otherwise
      // we won't be able to reply/retry,
    }
   
    // Messages get restored in a phase listener
   
    return viewRoot != null;
 

  private void saveActionParams(FacesContext context)
  {
    // Always preserve the FACES_VIEW_STATE parameter as per spec.
    // If portlet requests it, also preserve the rst of them.
    ExternalContext ec = context.getExternalContext();
    Map<String, Object> requestMap = ec.getRequestMap();
    Map<String, String[]> requestParameterMap = ec.getRequestParameterValuesMap();
    if (mPreserveActionParams.equals(Boolean.FALSE))
    {
      Map<String, String[]> m = new HashMap<String, String[]>(1);
      if (requestParameterMap != null &&
          requestParameterMap.containsKey(ResponseStateManager.VIEW_STATE_PARAM))
      {
        m.put(ResponseStateManager.VIEW_STATE_PARAM,
              requestParameterMap.get(ResponseStateManager.VIEW_STATE_PARAM));
      }
      else
      {
        // Manufacture a dummy VIEW_STATE_PARAM so Faces will see the render as being in a Postback
        // Issue this resolves is as follows:  Faces CommandLinks/Trinidad GoLinks are direct (href) links
        // to JSF views.  They are encoded as action URLs but generally submitted as GETs (without VIEW_STATE param)
        // When we fall through to the render following this action, the view is restored from cache (as usual)
        // and then we call lifecycle.execute.  The Faces RI (Mojarra) unfortunately has code in its restoreView handler
        // that if the UIViewRoot is already restored and isPostback is false (no VIEW_STATE param), then it sets
        // responseComplete.  By doing so the lifecycle.render is skipped and we get no markup output.
        // By creating a dummy VIEW_STATE param we work around this -- and as this parameter is updated at the end of the
        // render with the real one (because a save occurs) it shouldn't get out of sync with the cache view.
        m.put(ResponseStateManager.VIEW_STATE_PARAM,
              new String[] {NULL_VIEW_STATE_PARAM_VALUE});
      }
      requestMap.put(REQUEST_PARAMETERS, m);
    }
    else
    {
      // place the parameter map in the portlet request scope
      // so it will be promoted into the Bridge's request scope and hence
      // be available during render.
      Map<String, String[]> mutableParams = new HashMap<String, String[]>(requestParameterMap);
      requestMap.put(REQUEST_PARAMETERS, mutableParams);
    }
  }

  private void updateViewInfo(
    FacesContext context,
    String scopeId)
  {
    PortletContext portletContext = mPortletConfig.getPortletContext();

    // Get the request scope lock -- because its added during init it should
    // always be there.
    synchronized (portletContext.getAttribute(REQUEST_SCOPE_LOCK))
    {
      // get the managedScopeMap
      LRUMap requestScopeMap = (LRUMap) portletContext.getAttribute(REQUEST_SCOPE_MAP);

      if (requestScopeMap == null)
      {
        // Have only done renders to this point -- so no scope to update
        return;
      }

      // now see if this scope is in the Map
      Map<String, Object> scopeMap = requestScopeMap.get(scopeId);
      if (scopeMap == null)
      {
        // Scope has been previously removed -- so no scope to update
        return;
      }

      // Now get the RequestParameters from the scope
      @SuppressWarnings("unchecked")
      Map<String, String[]> requestParams = (Map<String, String[]>) scopeMap.get(REQUEST_PARAMETERS);

      if (requestParams == null)
      {
        requestParams = new HashMap<String, String[]>(1);
        scopeMap.put(REQUEST_PARAMETERS, requestParams);
      }

      // Prepare the value for storing as a preserved parameter
      // Store as an array of Strings with just one entry as per
      // portlet request
      String[] values = new String[1];
      // First make sure we have a value to update
      String updatedViewStateParam =
        (String) context.getExternalContext().getRequestMap().get(UPDATED_VIEW_STATE_PARAM);

      if (updatedViewStateParam != null)
      {
        values[0] = updatedViewStateParam;

        // finally update the value in the Map
        requestParams.put(ResponseStateManager.VIEW_STATE_PARAM, values);
      }
       
      // If this is the first render after an action the cached view will also
      // be in the scope.  Remove it so its not used again.
      // get the managedScopeMap
      scopeMap.remove(FACES_VIEWROOT);
    }
  }


  private LRUMap createRequestScopeMap(PortletContext portletContext)
  {
    // see if portlet has defined how many requestScopes to manage
    // for this portlet
    int managedScopes = DEFAULT_MAX_MANAGED_REQUEST_SCOPES;

    String managedScopesSetting =
      portletContext.getInitParameter(Bridge.MAX_MANAGED_REQUEST_SCOPES);
    if (managedScopesSetting != null)
    {
      managedScopes = Integer.parseInt(managedScopesSetting);
    }

    return new LRUMap(managedScopes);
  }

  @SuppressWarnings("unchecked")
  private RenderRequest restoreActionParams(FacesContext context)
  {
    // this is a little trickier then saving because there is no
    // corresponding set. Instead we wrap the request object and set it
    // on the externalContext.
    ExternalContext ec = context.getExternalContext();
    // Note: only available/restored if this scope was restored.
    Map<String, String[]> m = (Map<String, String[]>) ec.getRequestMap().get(REQUEST_PARAMETERS);

    // ensures current request returned if nothing to restore/wrap
    RenderRequest wrapped = (RenderRequest) ec.getRequest();
    if (m != null && !m.isEmpty())
    {
      wrapped = new BridgeRenderRequestWrapper(wrapped, m);
      ec.setRequest(wrapped);
    }
    return wrapped;
  }

  public void saveFacesMessageState(FacesContext context)
  {
    // get the messages from Faces Context
    Iterator<String> clientIds = context.getClientIdsWithMessages();
    if (clientIds.hasNext())
    {
      FacesMessageState state = new FacesMessageState();
      while (clientIds.hasNext())
      {
        String clientId = (String) clientIds.next();
        for (Iterator<FacesMessage> messages = context.getMessages(clientId); messages.hasNext();)
        {
          state.addMessage(clientId, messages.next());
        }
      }
      // save state in ViewRoot attributes
      Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
      requestMap.put(FACES_MESSAGES, state);
    }
  }

  private void restoreFacesMessageState(FacesContext context)
  {
    // Only restore for Render request
    if (context.getExternalContext().getRequest() instanceof RenderRequest)
    {
      Map<String, Object> map = context.getExternalContext().getRequestMap();

      // restoring FacesMessages
      FacesMessageState state = (FacesMessageState) map.get(FACES_MESSAGES);

      if (state != null)
      {
        for (String clientId:state.getClientIds())
        {
          for (FacesMessage message:state.getMessages(clientId))
          {
            context.addMessage(clientId, message);
          }
        }
      }
    }
  }

  private String getRequestScopeId(PortletRequest request)
  {
    boolean fromSession = false;
    String scopeId = request.getParameter(REQUEST_SCOPE_ID_RENDER_PARAM);

    if (scopeId == null)
    {
      PortletSession session = request.getPortletSession();
      if (session != null)
      {
        scopeId = (String) session.getAttribute(BRIDGE_PACKAGE_PREFIX + REQUEST_SCOPE_ID_RENDER_PARAM);
        fromSession = true;
      }
    }
   
    // Only use this scope if it matches the current request mode
    if (scopeId != null)
    {
      // Its possible we didn't detect the mode change but its the wrong scope
      // as the scope is encoded with the mode -- confirm its right
      StringBuffer sb = new StringBuffer(10);
      String modeCheck = sb.append(":").append(request.getPortletMode().toString()).append(":").toString();
      if (scopeId.indexOf(modeCheck) < 0 )
      {
        // scope is for a different mode
        scopeId = null;
      }
    }
       
    return scopeId;
  }

  private String initBridgeRequestScope(PortletRequest request, StateAwareResponse response)
  {

    // Generate an RMI UID, which is a unique identifier WITHIN the local
    // host. This will be used as the new lifecyleID
    UID uid = new UID();
    String requestScopeId = qualifyScopeId(mPortletConfig.getPortletName(),
                                           request.getPortletSession(true).getId(),
                                           request.getPortletMode().toString(),
                                           uid.toString());

    if (response != null)
    {
      // set in response render parameter so will receive in future calls
      // however don't store internally until there is specific state to
      // manage
      response.setRenderParameter(REQUEST_SCOPE_ID_RENDER_PARAM, requestScopeId);
    }
    else
    {
      // Must be a render/resource request before a scope has been established
      // store in session until next action
      PortletSession session = request.getPortletSession(true);
      session.setAttribute(BRIDGE_PACKAGE_PREFIX + REQUEST_SCOPE_ID_RENDER_PARAM, requestScopeId);
    }

    return requestScopeId;
  }

  private void saveBridgeRequestScopeData(FacesContext context, String scopeId,
                                          List<String> preExistingList)
  {
    // Store the RequestMap @ the bridge's request scope
    putBridgeRequestScopeData(scopeId,
                              copyRequestMap(context.getExternalContext().getRequestMap(), preExistingList));

    // flag the data so can remove it if the session terminates
    // as its unlikely useful if the session disappears
    watchScope(context, scopeId);
  }

  @SuppressWarnings("unchecked")
  private void putBridgeRequestScopeData(String scopeId, Map<String, Object> o)
  {
    PortletContext portletContext = mPortletConfig.getPortletContext();

    // Get the request scope lock -- because its added during init it should
    // always be there.
    synchronized (portletContext.getAttribute(REQUEST_SCOPE_LOCK))
    {
      // get the managedScopeMap
      LRUMap requestScopeMap = (LRUMap) portletContext.getAttribute(REQUEST_SCOPE_MAP);

      if (requestScopeMap == null)
      {
        requestScopeMap = createRequestScopeMap(portletContext);
        portletContext.setAttribute(REQUEST_SCOPE_MAP, requestScopeMap);
      }
     
      if (BridgeUtil.getPortletRequestPhase() == Bridge.PortletPhase.RESOURCE_PHASE)
      {
        // Because we didn't restore them at the beginning of the request
        // Merge any existing scope attributes that weren't written during this request
        Map<String, Object> existing = requestScopeMap.get(scopeId);
        if (existing != null)
        {
          Iterator<Map.Entry<String, Object>> i = existing.entrySet().iterator();
          while (i.hasNext())
          {
            Map.Entry<String, Object> e = i.next();
            if (!o.containsKey(e.getKey()))
            {
              o.put(e.getKey(), e.getValue());
            }
          }
        }
      }
     
      requestScopeMap.put(scopeId, o);
    }
  }

  private Map<String, Object> copyRequestMap(Map<String, Object> m, List<String> preExistingList)
  {
    Map<String, Object> copy = new HashMap<String, Object>(m.size());

    for (Map.Entry<String, Object> entry:m.entrySet())
    {
      // TODO -- restore the ACTION PARAMS if there

      // Don't copy any of the portlet or Faces objects
      String key = entry.getKey();
      Object value = entry.getValue();
      if (!isExcludedFromBridgeRequestScope(key, value, preExistingList))
      {
        copy.put(key, value);
      }
    }
    return copy;
  }

  @SuppressWarnings("unchecked")
  private List<String> getRequestAttributes(PortletRequest request)
  {
    List<String> requestAttributeNames =  Collections.list((Enumeration<String>) request.getAttributeNames());
    // place on the request for use here and in the servletRequestAttributeListener
    if (requestAttributeNames != null)
    {
      request.setAttribute(PREEXISTING_ATTRIBUTE_NAMES, requestAttributeNames);
    }
    return requestAttributeNames;
  }

  private boolean isExcludedFromBridgeRequestScope(String key, Object value, List<String> preExistingList)
  {
    return ((value != null && value.getClass().getAnnotation(ExcludeFromManagedRequestScope.class) != null) ||
            (preExistingList != null && preExistingList.contains(key)) ||
            isPreDefinedExcludedObject(key, value) ||
            isConfiguredExcludedAttribute(key));
  }

  private boolean isPreDefinedExcludedObject(String s, Object o)
  {
    if (o != null && (o instanceof PortletConfig || o instanceof PortletContext ||
      o instanceof PortletRequest || o instanceof PortletResponse || o instanceof PortletSession ||
      o instanceof PortletPreferences || o instanceof PortalContext || o instanceof FacesContext ||
      o instanceof ExternalContext || o instanceof ServletConfig || o instanceof ServletContext ||
      o instanceof ServletRequest || o instanceof ServletResponse || o instanceof HttpSession))
      return true;
    else
    {
      return isInNamespace(s, "javax.portlet.") ||
      isInNamespace(s, "javax.portlet.faces.") ||
      isInNamespace(s, "javax.faces.") ||
      isInNamespace(s, "javax.servlet.") ||
      isInNamespace(s, "javax.servlet.include.") ||
      isInNamespace(s, "org.apache.myfaces.portlet.faces.") ||
      // our ExternalContext uses this prefix internally to append to url which might
      // contain another '.' -- so exclude all that are prefixed with this
      s.startsWith("org.apache.myfaces.portlet.faces.context.");
    }
  }

  private boolean isConfiguredExcludedAttribute(String s)
  {
    if (mExcludedRequestAttributes == null)
    {
      return false;
    }

    if (mExcludedRequestAttributes.contains(s))
    {
      return true;
    }

    // No direct match -- walk through this list and process namespace checks
    Iterator<String> i = mExcludedRequestAttributes.iterator();
    while (i.hasNext())
    {
      String exclude = i.next();
      if (exclude.endsWith("*"))
      {
        if (isInNamespace(s, exclude.substring(0, exclude.length() - 1)))
        {
          return true;
        }
      }
    }
    return false;
  }

  private boolean isInNamespace(String s, String namespace)
  {
    // This is a non-recursive check so s must be the result of removing the namespace.
    if (s.startsWith(namespace))
    {
      // extract entire namespace and compare
      s = s.substring(0, s.lastIndexOf('.') + 1);
      return s.equals(namespace);
    }
    return false;
  }

  @SuppressWarnings("unchecked")
  private boolean restoreBridgeRequestScopeData(PortletRequest request, String scopeId)
    throws BridgeException
  {

    PortletContext portletContext = mPortletConfig.getPortletContext();
    Map<String, Object> m;
   
    //TODO: Since this is a private method, is it easier to ensure scope id is not null here thus replacing this with
    //an assert
    if (scopeId == null)
    {
      return false;
    }

    // Get the data from the scope
    synchronized (portletContext.getAttribute(REQUEST_SCOPE_LOCK))
    {
      // get the managedScopeMap
      LRUMap requestScopeMap = (LRUMap) portletContext.getAttribute(REQUEST_SCOPE_MAP);
      // No scope for all renders before first action to this portletApp
      if (requestScopeMap == null)
      {
        return false;
      }

      m = requestScopeMap.get(scopeId);
      if (m == null)
      {
        return false;
      }
    }
   
    Set<Map.Entry<String, Object>> s = m.entrySet();
    Iterator<Map.Entry<String, Object>> i = s.iterator();
    while (i.hasNext())
    {
      Map.Entry<String, Object> e = i.next();
      request.setAttribute(e.getKey(), e.getValue());
    }
   
    return true;
  }

  private boolean removeFromBridgeRequestScopeData(FacesContext context, String scopeId,
                                                   String key)
  {
    PortletContext portletContext = mPortletConfig.getPortletContext();
    Map<String, Object> m = null;

    //TODO: Since this is a private method, is it easier to ensure scope id is not null here thus replacing this with
    //an assert
    if (scopeId == null)
    {
      return false;
    }

    // Get the data from the scope
    synchronized (portletContext.getAttribute(REQUEST_SCOPE_LOCK))
    {
      // get the managedScopeMap
      LRUMap requestScopeMap = (LRUMap) portletContext.getAttribute(REQUEST_SCOPE_MAP);
      // No scope for all renders before first action to this portletApp
      if (requestScopeMap == null)
      {
        return false;
      }

      m = requestScopeMap.get(scopeId);
      if (m != null)
      {
        return m.remove(key) != null;
      }

    }

    return false;
  }

  /*
   * A scope is qualified first by the portlet this scope has been created for
   * and then second by the specific session this scope is used in.  By doing
   * this we are able to remove this specific scope, all the scopes associated
   * with a particular session, or all the scopes associated with a particular
   * portlet regardless of sessions.
   */

  private String qualifyScopeId(String portletId, String sessionId, String mode, String scopeId)
  {
    // a qualified scope Id must at a minimum be qualified by a portletId
    if (portletId == null) portletId = mPortletConfig.getPortletName();
   
    StringBuffer sb = new StringBuffer(portletId);
    sb.append(':');
    if (sessionId != null)
    {
      sb.append(sessionId);
      sb.append(':');
      if (mode != null)
      {
        sb.append(mode);
        sb.append(':');
        if (scopeId != null)
        {
          sb.append(scopeId);
        }
      }
    }

    return sb.toString();
  }

  private void watchScope(FacesContext context, String scopeId)
  {
    PortletSession session = (PortletSession) context.getExternalContext().getSession(true);
    if (session != null)
    {
      RequestScopeListener scopeListener =
        (RequestScopeListener) session.getAttribute(REQUEST_SCOPE_LISTENER);
      if (scopeListener == null)
      {
        // only store the qualified prefix
        // if invalidated we walk the entire REQUEST_SCOPE Map and
        // remove
        // every scope that starts with this prefix.
        session.setAttribute(REQUEST_SCOPE_LISTENER,
                             new RequestScopeListener(qualifyScopeId(mPortletConfig.getPortletName(),
                                                                     session.getId(), null, null)));
      }
    }
  }
 
  private boolean processIncomingPublicRenderParameters(FacesContext context, PortletRequest request)
  {
    boolean paramsMapped = false;
   
    if (mPublicParameterMappings == null) return false;
   
    Enumeration<String> e = mPortletConfig.getPublicRenderParameterNames();
    if (!e.hasMoreElements()) return false;
    Map<String, String[]>  m = request.getPublicParameterMap();
   
    Application app = context.getApplication();
   
    while (e.hasMoreElements())
    {
      String name = e.nextElement();
      String prefixedName = new StringBuffer(mPortletConfig.getPortletName()).append(":").append(name).toString();
      boolean isPrefixed = mPublicParameterMappings.containsKey(prefixedName);
     
      // See if we have a mapping for this one
      if (isPrefixed || mPublicParameterMappings.containsKey(name))
      {
        // See if a parameter has been passed
        if (m.containsKey(name))
        {
          String[] newVals = m.get(name);
          String expr = mPublicParameterMappings.get((isPrefixed) ? prefixedName : name);
         
          // now evaluate the expression
          ValueExpression valExpr = app.getExpressionFactory().createValueExpression(context.getELContext(),
            expr, String.class);
          String curVal = (String) valExpr.getValue(context.getELContext());
          // Only update if new value
          // Since we are accessing via EL can't distinguish between null and empty string
          if (curVal == null || !curVal.equals(newVals[0]))
          {
            valExpr.setValue(context.getELContext(), newVals[0]);
            paramsMapped = true;
          }
        }
        else
        {
          // parameter not passed -- but we have a mapping -- so
          // set it to null if not currently null;
          String expr = mPublicParameterMappings.get((isPrefixed) ? prefixedName : name);
         
          // now evaluate the expression
          ValueExpression valExpr = app.getExpressionFactory().createValueExpression(context.getELContext(),
            expr, String.class);
         
          try
          {
            String curVal = (String) valExpr.getValue(context.getELContext());
            // Only update if new value
            // In EL can't distinguish between null and empty string
            if (curVal != null && curVal.length() > 0)
            {
              valExpr.setValue(context.getELContext(), null);
              paramsMapped = true;
            }
          }
          catch (Exception ex)
          {
            // Note:  expression may not evaluate because PRP mappings are in face-config.xml and multiple portlets
            // may be sharing -- hence this portlet may not have an expression mapping
            ;
          }
        }
      }
    }
   
    if (paramsMapped && mProcessPRPHandler != null)
    {
      // fire a PublicParametersChanged event so any needed computation
      // based on the new values can occur
      mProcessPRPHandler.processUpdates(context);
    }
    return paramsMapped;
  }
 
  private void processOutgoingPublicRenderParameters(FacesContext context, PortletRequest request, StateAwareResponse response)
  {
    if (mPublicParameterMappings == null) return;
   
    Application app = context.getApplication();
    Enumeration<String> e = mPortletConfig.getPublicRenderParameterNames();
    while (e.hasMoreElements())
    {
      String s = e.nextElement();
      String expr = mPublicParameterMappings.get(new StringBuffer(mPortletConfig.getPortletName()).append(":").append(s).toString());
      if (expr == null)
      {
        expr = mPublicParameterMappings.get(s);
        if (expr == null) continue;
      }
    
      // now evaluate the expression
      ValueExpression valExpr = app.getExpressionFactory().createValueExpression(context.getELContext(),
        expr, String.class);
      try
      {
        String value = (String) valExpr.getValue(context.getELContext());
       
        // only set in response if the value has changed
        Map<String, String[]> m = request.getPublicParameterMap();
        if (value != null)
        {
          String[] requestVals = m.get(s);
          // only set if the values are different
          if (requestVals == null || !requestVals[0].equals(value))
          {
            response.setRenderParameter(s, value);
          }
        }
        // see if we need to remove it
        else if (m.containsKey(s))
        {
          response.removePublicRenderParameter(s);
        }
      }
      catch (Exception exception)
      {
        // Do nothing
        ;
      }
    }
  }

  private void finalizeActionResponse(FacesContext context)
    throws IOException
  {
   
    // First reset the no scope flag as we only care about the
    // action result
    context.getExternalContext().getRequestMap().put(
      PortletExternalContextImpl.NO_SCOPE, Boolean.FALSE);

    // We rely on Faces ExternalContext.encodeActionURL to do the heavy
    // lifting here. First we construct a true actionURL using the viewId
    // for the view that is the target of the navigation. Then we call
    // encodeActionURL passing this URL. encodeActionURL encodes into
    // ActionResponse sufficient information (pulled from the supplied
    // actionURL) so that it (the EXteranlContext) can decode the
    // information
    // in the subsequent render request(s).

    String viewId = context.getViewRoot().getViewId();

    // Get and remove any querystring params we stashed that were part of the target viewId
    // We support viewId targets in faces-config.xml so can set things like modes
    String qs = (String) context.getViewRoot().getAttributes().remove(VIEWID_QUERYSTRING_ATTRIBUTE);

    // don't care about the return as it means nothing
    ViewHandler viewHandler = context.getApplication().getViewHandler();
    String viewURL = viewHandler.getActionURL(context, viewId);
    if (qs != null && qs.length() > 1)
    {
      // see whether the viewURL contains any params
      // Now add the parameters back on (if needed)
      if (viewURL.indexOf('?') < 0)
      {
        viewURL = viewURL.concat(qs);
      }
      else
      {
        viewURL = viewURL + "&" + qs.substring(1);
      }
    }

    // don't care about the return as it means nothing
    context.getExternalContext().encodeActionURL(viewURL);

  }

  // notify this scope's attributes that they are being removed
  private void notifyPreDestroy(Map<String, Object> scope)
  {
    Set<Map.Entry<String, Object>> s = scope.entrySet();
    Iterator<Map.Entry<String, Object>> i = s.iterator();
    while (i.hasNext())
    {
      notifyPreDestroy(i.next().getValue());
    }
  }

  // notify this scope's attributes that they are being removed
  private void notifyPreDestroy(Object o)
  {
    Method[] methods = o.getClass().getMethods();
    for (int m = 0; m < methods.length; m++)
    {
      if (methods[m].isAnnotationPresent(BridgePreDestroy.class))
      {
        try
        {
          methods[m].invoke(o, null);
        }
        catch (Exception e)
        {
          // TODO: log problem
          // do nothing and forge ahead
          ;
        }
      }
    }
  }

  private void removeRequestScopes(String scopePrefix)
  {

    if (scopePrefix == null || mPortletConfig == null)
      return; // Nothing to do -- later case is the session is destroyed after the context

    // Get the RequestScope Map and remove all entries/scopes with this prefix
    PortletContext portletContext = mPortletConfig.getPortletContext();

    // Get the request scope lock -- because its added during init it should
    // always be there.
    Object lock = portletContext.getAttribute(REQUEST_SCOPE_LOCK);
    if (lock == null)
      return;

    synchronized (lock)
    {
      // get the managedScopeMap
      LRUMap requestScopeMap = (LRUMap) portletContext.getAttribute(REQUEST_SCOPE_MAP);

      if (requestScopeMap != null)
      {
        Iterator<String> iterator = requestScopeMap.keySet().iterator();
        while (iterator.hasNext())
        {
          String scopeId = iterator.next();
          if (scopeId != null && scopeId.startsWith(scopePrefix))
          {
            iterator.remove();
          }
        }
      }
    }
  }

  private void readExcludedAttributesFromFacesConfig(PortletContext context,
                                                     List<String> excludedAttributes)
  {
    FacesConfigurationProcessor processor = new FacesConfigurationProcessor(context);
    List<String> list = processor.getExcludedAttributes();
   
    if (list == null)
    {
      return;
    }
   
    ListIterator<String> i = (ListIterator<String>) list.listIterator();
    while (i.hasNext())
    {
      String attr = i.next();
      if (!excludedAttributes.contains(attr))
      {
        excludedAttributes.add(attr);
      }
    }
  }

  /* Implement the PhaseListener methods */

  public PhaseId getPhaseId()
  {
    return PhaseId.RESTORE_VIEW;
  }

  public void beforePhase(PhaseEvent event)
  {
    FacesContext context = event.getFacesContext();
   
    // There is only one lifecycle instance within a webapp -- and lifecycles aren't thread safe
    // This means we may have multiple listeners registered and/or multiple simulataneous requests
    // So only do the work if the FacesContext of the event is equal to the one in the current thread
    if (context != FacesContext.getCurrentInstance())
    {
      return;
    }
   
    // only set renderresponse if in RESTORE_VIEW phase (and we are the listener for the targeted portlet)
    String pName = (String) context.getExternalContext().getRequestMap().get(PORTLET_NAME_ATTRIBUTE);
   
    if (event.getPhaseId() == PhaseId.RESTORE_VIEW)
    {
      // If the viewRoot has been preset before the lifecycle is run then Faces
      // uses it but only after reseting its locale to the one requested in the request.
      // Since the bridge relies on this presetting ability to transfer the view from the
      // action to the render phase we run into a problem if the action changes the views locale.
      // This because the view that results/created in from the action's navigation acquires
      // the locale of the action's viewRoots locale.  This action causes ViewRoot A to change its
      // locale to DE -- the navigation causes us to create a new ViewRoot B which Faces sets to
      // have/use the DE locale.  The render hits this preset code which causes the locale of the
      // ViewRoot to reset to the requests preferred locale.  Ie.. we lose the locale.  This is why the
      // CarDemo's locale mechanism wasn't working.  To fix:  check if we are using the preset
      // ability here -- and if so cache the locale in the request.  On the afterPhase -- reset the locale
      // back to the what it originally was.

      UIViewRoot viewRoot = context.getViewRoot();
      if (viewRoot != null)
      {
        Locale l = viewRoot.getLocale();
        if (l != null)
        {
          context.getExternalContext().getRequestMap().put(CACHED_VIEWROOT_LOCALE, l);
        }
      }
    }
    return;
  }

  public void afterPhase(PhaseEvent event)
  {
    // only set renderresponse if in RESTORE_VIEW phase
    FacesContext context = event.getFacesContext();
   
    // There is only one lifecycle instance within a webapp -- and lifecycles aren't thread safe
    // This means we may have multiple listeners registered and/or multiple simulataneous requests
    // So only do the work if the FacesContext of the event is equal to the one in the current thread
    if (context != FacesContext.getCurrentInstance())
    {
      return;
    }
   
    // only set renderresponse if in RESTORE_VIEW phase (and we are the listener for the targeted portlet)
    String pName = (String) context.getExternalContext().getRequestMap().get(PORTLET_NAME_ATTRIBUTE);
   
    if (event.getPhaseId() == PhaseId.RESTORE_VIEW)
    {    
      // restore ViewRoot locale (see comment in beforePhase)
      Locale l = (Locale) context.getExternalContext().getRequestMap().get(CACHED_VIEWROOT_LOCALE);
      if (l != null)
      {
        UIViewRoot viewRoot = context.getViewRoot();
        if (viewRoot != null)
        {
          viewRoot.setLocale(l);
        }
      }
     
      // Now restore the Faces Messages
      restoreFacesMessageState(context);
     
      // Now process any PublicRenderRarameter mappings
      boolean mappedPublicParams = false;
     
      // Only process PublicRenderParameters once
      if (context.getExternalContext().getRequestMap().get(PROCESSED_PUBLIC_PARAMS) == null)
      {
        // process public render parameters -- note pass in the portlet request as this
        // exposes the PPR where the one in the ExternalContext doesn't
        mappedPublicParams = processIncomingPublicRenderParameters(context, (PortletRequest) context.getExternalContext().getRequest());
        context.getExternalContext().getRequestMap().put(PROCESSED_PUBLIC_PARAMS, Boolean.TRUE);
      }
     
      switch (BridgeUtil.getPortletRequestPhase())
      {
      case RENDER_PHASE:
      case EVENT_PHASE:
        // skip rest of action processing if no PRPs were mapped                 
        if (!mappedPublicParams) context.renderResponse();
      }
    }
  }

  private final class LRUMap
    extends LinkedHashMap<String, Map<String, Object>>
  {

    /**
     *
     */
    private static final long serialVersionUID = 4372455368577337965L;
    private int mMaxCapacity;

    public LRUMap(int maxCapacity)
    {
      super(maxCapacity, 1.0f, true);
      mMaxCapacity = maxCapacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Map<String, Object>> eldest)
    {
      // manually remove the entry so we can ensure notifyPreDestroy is only
      // called once
      if (size() > mMaxCapacity)
      {
        // side effect of this call is to notify PreDestroy
        remove(eldest.getKey());
      }
      return false;
    }

    public Map<String, Object> remove(String key)
    {
      dumpScopeId(key, "RemovePhase");
      Map<String, Object> o = super.remove(key);
      // notify attributes maintained in this object (map) they are going away
      // Method in the outer BridgeImpl class
      if (o != null)
        notifyPreDestroy(o);
      return o;
    }

    public Map<String, Object> put(String key, Map<String, Object> value)
    {
      Map<String, Object> o = super.put(key, value);
      // notify attributes maintained in this object (map) they are going away
      // Method in the outer BridgeImpl class
      if (o != null)
        notifyPreDestroy(o);
      return o;
    }

  }

  // TODO: Should we store these as attributes of the ViewTree??? It would
  // work as
  // everything is serializable. -- Issue is we need to implement a
  // PhaseListener to
  // to deal with this -- at the moment I prefer to isolate the Faces
  // extensions from this
  // detail and leave it all in this controller part.

  private final class FacesMessageState
    implements Serializable
  {
    /**
     *
     */
    private static final long serialVersionUID = 8438070672451887050L;
    // For saving and restoring FacesMessages
    private LinkedHashMap<String, List<FacesMessage>> mMessages = new LinkedHashMap<String, List<FacesMessage>>(); // key=clientId;

    // value=FacesMessages

    public void addMessage(String clientId, FacesMessage message)
    {
      List<FacesMessage> list = mMessages.get(clientId);
      if (list == null)
      {
        list = new ArrayList<FacesMessage>();
        mMessages.put(clientId, list);
      }
      list.add(message);
    }

    public List<FacesMessage> getMessages(String clientId)
    {
      List<FacesMessage> list = mMessages.get(clientId);
      if (list != null)
      {
        return list;
      }
      else
      {
        return Collections.emptyList();
      }
    }

    public Set<String> getClientIds()
    {
      return mMessages.keySet();
    }
  }

  private final class RequestScopeListener
    implements HttpSessionBindingListener
  {
    String mScopePrefix = null;

    public RequestScopeListener(String scopePrefix)
    {
      mScopePrefix = scopePrefix;
    }

    public void valueBound(HttpSessionBindingEvent event)
    {

    }

    public void valueUnbound(HttpSessionBindingEvent event)
    {
      // Call is in the BridgeImpl class
      removeRequestScopes(mScopePrefix);
    }
  }
}
TOP

Related Classes of org.apache.myfaces.portlet.faces.bridge.BridgeImpl

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.