/* 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.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.Map;
import java.util.Set;
import javax.el.ELContext;
import javax.el.ELContextEvent;
import javax.el.ELContextListener;
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.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.faces.Bridge;
import javax.portlet.faces.BridgeDefaultViewNotSpecifiedException;
import javax.portlet.faces.BridgeException;
import javax.portlet.faces.BridgeNotAFacesRequestException;
import javax.portlet.faces.BridgeUninitializedException;
import javax.portlet.faces.annotation.BridgePreDestroy;
import javax.portlet.faces.annotation.BridgeRequestScopeAttributeAdded;
import javax.portlet.faces.annotation.ExcludeFromManagedRequestScope;
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.BridgeRenderRequestWrapper;
import org.apache.myfaces.portlet.faces.context.PortletExternalContextImpl;
import org.apache.myfaces.portlet.faces.el.PortletELContextImpl;
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 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 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";
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";
private static final String REQUEST_PARAMETERS = "org.apache.myfaces.portlet.faces.includeInScope.requestParameters";
private 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 int DEFAULT_MAX_MANAGED_REQUEST_SCOPES = 100;
private Boolean mPreserveActionParams = Boolean.FALSE;
private List<String> mExcludedRequestAttributes = null;
private PortletConfig mPortletConfig = null;
private FacesContextFactory mFacesContextFactory = null;
private LifecycleFactory mLifecycleFactory = null;
private String mLifecycleId = null;
private List<String> mFacesMappings = null;
private boolean mInitialized = false;
private Map<String,String> mDefaultViewIdMap = 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();
// get preserveActionParams and excludedAttributes configuration settings.
mPreserveActionParams = (Boolean) portletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + mPortletConfig.getPortletName() +
"." + Bridge.PRESERVE_ACTION_PARAMS);
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);
// place on the request for use here and in the servletRequestAttributeListener
if (preExistingAttributes != null)
{
request.setAttribute(PREEXISTING_ATTRIBUTE_NAMES, preExistingAttributes);
}
FacesContext context = null;
String scopeId = null;
try
{
Lifecycle lifecycle = getLifecycle();
// Get the FacesContext instance for this request
context = getFacesContext(request, response, lifecycle, null);
// For actions we only execute the lifecycle phase
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);
// 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 (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.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(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;
// 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 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 && (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);
}
// never restore a scope if relying on render redirect cache/client directed view/mode change
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 = request.getParameter(REQUEST_SCOPE_ID_RENDER_PARAM);
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;
}
}
}
restoredScope = restoreBridgeRequestScopeData(request, scopeId);
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.
restoreFacesView(context, scopeId);
}
// Ensure the ContentType is set before rendering
if (extCtx.getResponseContentType() == null)
{
response.setContentType(extRequest.getResponseContentType());
}
// 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);
// 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();
// When exception occurs remove stored scope so don't
// get stuck replaying the error when/if user refreshes
if (scopeId != null)
{
removeRequestScopes(scopeId);
}
mPortletConfig.getPortletContext().log("Exception thrown in doFacesRequest:render: " + e.getMessage() + " : " + e.toString());
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(
RenderRequest request,
RenderResponse 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.
// add self as PhaseListener to prevent action phases from
// executing
lifecycle.addPhaseListener(this);
try
{
lifecycle.execute(context);
}
catch (Exception e)
{
// When exception occurs remove stored scope so don't
// get stuck replaying the error when/if user refreshes
if (scopeId != null)
{
removeRequestScopes(scopeId);
}
// now rethrow the exception as a BridgeException
if (!(e instanceof BridgeException))
{
throw new 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)
{
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;
}
// When we have navigated to this view between the action and render
// the initial VIEW_STATE_PARAM reflects the actions view -- update
// here to the one from this render so refresh will work.
if (scopeId != null)
{
// Don't update if we have redirected during this render instead remove the scope
if (!redirectedDuringRender)
{
updateViewInfo(context, scopeId);
}
else
{
removeRequestScopes(scopeId);
}
}
}
public void destroy()
{
if (!mInitialized)
// do nothing if destroy an uninitialzed bridge
return;
mInitialized = false;
// 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 in an action this means the attribute has been removed 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.ACTION_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.ACTION_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 defaultViewId = encodeMode(mode, viewIdDefaultMap.get(mode));
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();
}
}
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[]> paramMap = new LinkedHashMap(redirectParams.numParameters());
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());
}
paramMap.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, paramMap, false);
}
private void redirectRender(FacesContext context,
Lifecycle lifecycle,
RenderRequest request,
RenderResponse response,
QueryString redirectParams,
List<String> preExistingAttrs)
{
// close the FacesContext
context.release();
// Now reset attributes to those that existed before we were called
// reset request attributes to those that existed before we were called.
String[] names = new String[400];
int count = 0;
Enumeration<String> e = request.getAttributeNames();
while (e.hasMoreElements())
{
String name = e.nextElement();
if (!preExistingAttrs.contains(name))
{
names[count++] = name;
}
}
for (int j = 0; j < count; j++)
{
request.removeAttribute(names[j]);
}
// 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 (mLifecycleFactory == null)
{
mLifecycleFactory =
(LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
mLifecycleId =
mPortletConfig.getPortletContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
if (mLifecycleId == null)
{
mLifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
}
}
return mLifecycleFactory.getLifecycle(mLifecycleId);
}
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 void 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 and the saved Bridge Request
// Scope
requestMap.remove(FACES_VIEWROOT);
removeFromBridgeRequestScopeData(context, scopeId, FACES_VIEWROOT);
}
// Messages get restored in a phase listener
}
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))
{
if (requestMap != null && requestParameterMap != null &&
requestParameterMap.containsKey(ResponseStateManager.VIEW_STATE_PARAM))
{
Map<String, String[]> m = new HashMap<String, String[]>(1);
m.put(ResponseStateManager.VIEW_STATE_PARAM,
requestParameterMap.get(ResponseStateManager.VIEW_STATE_PARAM));
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 initBridgeRequestScope(ActionRequest request, ActionResponse 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());
// 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);
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);
}
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)
{
return Collections.list((Enumeration<String>) request.getAttributeNames());
}
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 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();
// don't care about the return as it means nothing
ViewHandler viewHandler = context.getApplication().getViewHandler();
String viewURL = viewHandler.getActionURL(context, viewId);
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)
{
// do nothing
return;
}
public void afterPhase(PhaseEvent event)
{
// only set renderresponse if in RESTORE_VIEW phase
if (event.getPhaseId() == PhaseId.RESTORE_VIEW)
{
FacesContext context = event.getFacesContext();
// Now restore the Faces Messages
restoreFacesMessageState(context);
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);
}
}
}