/* 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 java.util.Vector;
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.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.MimeResponse;
import javax.portlet.PortalContext;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
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.filter.PortletResponseWrapper;
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.portlet.filter.PortletResponseWrapper;
import javax.portlet.filter.RenderResponseWrapper;
import javax.portlet.filter.ResourceResponseWrapper;
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/ExternalContext 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 static final String LOGGING_ENABLED = "org.apache.myfaces.portlet.faces.loggingEnabled";
public static final String WRITE_BEHIND_RESPONSE = "org.apache.myfaces.portlet.faces.writeBehindResponse";
// 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 SCOPE_VIEW_KEY = "org.apache.myfaces.portlet.faces.includeInScope.scopeViewKey";
private static final String CHILD_RESOURCE_REQUEST_SCOPE_MAP = "org.apache.myfaces.portlet.faces.includeInScope.childResourceRequestScopeMap";
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 Boolean mDebugLoggingEnabled;
private Map<String,String> mDefaultViewIdMap = null;
private Class<? extends BridgeWriteBehindResponse> mWriteBehindRenderResponseWrapper = null;
private Class<? extends BridgeWriteBehindResponse> mWriteBehindResourceResponseWrapper = 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();
// See if the bridge should log informational messages
mDebugLoggingEnabled = (Boolean) portletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + mPortletConfig.getPortletName() +
"." + BridgeImpl.LOGGING_ENABLED);
if (mDebugLoggingEnabled == null) mDebugLoggingEnabled = Boolean.FALSE;
// 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);
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);
}
// acquire any config information in faces-config.xml
processFacesConfiguration(portletContext);
// 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 whether logging is enabled for other subsystems to see
request.setAttribute(BridgeImpl.LOGGING_ENABLED, mDebugLoggingEnabled);
// 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)
{
if (mDebugLoggingEnabled.booleanValue())
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)
{
if (!mDebugLoggingEnabled.booleanValue()) return;
// 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");
}
}
private void dumpParameters(PortletRequest request)
{
if (!mDebugLoggingEnabled.booleanValue()) return;
// Get the data from the scope
PortletContext ctx = mPortletConfig.getPortletContext();
ctx.log("dumpParameters: ");
Map<String, String[]> m = request.getParameterMap();
for (Map.Entry<String, String[]> e : m.entrySet())
{
String key = e.getKey();
String[] values = e.getValue();
for (int i = 0; i < values.length; i++)
{
ctx.log(" key: " + key + " value: " + values[i]);
}
}
ctx.log("dumpPublicParameters: ");
m = request.getPublicParameterMap();
for (Map.Entry<String, String[]> e : m.entrySet())
{
String key = e.getKey();
String[] values = e.getValue();
for (int i = 0; i < values.length; i++)
{
ctx.log(" key: " + key + " value: " + values[i]);
}
}
ctx.log("dumpPrivateParameters: ");
m = request.getPrivateParameterMap();
for (Map.Entry<String, String[]> e : m.entrySet())
{
String key = e.getKey();
String[] values = e.getValue();
for (int i = 0; i < values.length; i++)
{
ctx.log(" key: " + key + " value: " + values[i]);
}
}
}
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);
// 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 whether logging is enabled for other subsystems to see
request.setAttribute(BridgeImpl.LOGGING_ENABLED, mDebugLoggingEnabled);
// 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);
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;
boolean removeScope = 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())
{
// clear scopeid render parameter
removeScope = true;
return;
}
if (result != null)
{
context.getApplication().getNavigationHandler().handleNavigation(context, result.getFromAction(), result.getOutcome());
// the navigation rule might indicate a redirect
if (context.getResponseComplete())
{
// clear scopeid render parameter
removeScope = true;
return;
}
}
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);
}
else
{
removeScope = true;
}
}
catch (Exception e)
{
if (!mDebugLoggingEnabled.booleanValue())
mPortletConfig.getPortletContext().log("Exception thrown in doFacesRequest:event", e);
if (!(e instanceof BridgeException))
{
e = new BridgeException(e);
}
throw (BridgeException) e;
}
finally
{
if (removeScope)
{
// Because we automatically copy forward all render parameters when the scope is not carried forward we must clear its param
removeRequestScopes(scopeId);
}
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));
}
dumpParameters(request);
// 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 whether logging is enabled for other subsystems to see
request.setAttribute(BridgeImpl.LOGGING_ENABLED, mDebugLoggingEnabled);
// 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 (mWriteBehindRenderResponseWrapper != null)
{
request.setAttribute(BridgeImpl.WRITE_BEHIND_RESPONSE, mWriteBehindRenderResponseWrapper);
}
// 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();
if (!mDebugLoggingEnabled.booleanValue())
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
// Don't update if we have redirected during this render instead remove the scope
if (scopeId != null && !redirectedDuringRender)
{
context.getExternalContext().getRequestMap().put(SCOPE_VIEW_KEY, getScopeViewKey(context.getExternalContext()));
saveFacesMessageState(context);
saveBridgeRequestScopeData(context, scopeId, preExistingAttributes);
updateViewInfo(context, scopeId);
}
else
{
if (scopeId != null) removeRequestScopes(scopeId);
// start a new/empty in session scope -- merely add the key used to discriminate whether
// a follow-on resource request targets the same view as the render (PPR) or a different view (iFrame)
scopeId = initBridgeRequestScope(request, null);
HashMap<String, Object> m = (HashMap<String, Object>) new HashMap();
m.put(SCOPE_VIEW_KEY, getScopeViewKey(context.getExternalContext()));
putBridgeRequestScopeData(scopeId, m);
updateViewInfo(context, 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);
// set whether logging is enabled for other subsystems to see
request.setAttribute(BridgeImpl.LOGGING_ENABLED, mDebugLoggingEnabled);
// Determine whether this is a Faces resource or a regular one.
if (!isFacesResourceRequest(request))
{
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 (mWriteBehindResourceResponseWrapper != null)
{
request.setAttribute(BridgeImpl.WRITE_BEHIND_RESPONSE, mWriteBehindResourceResponseWrapper);
}
// 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;
String scopeId = 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();
// getting the scopeId is delayed until after getting the FacesContext
// because a resource request could be as a result of an embedded iFrame
// i.e. a child scope of the original render -- in this case don't use the render
// scope but the correct one contained within it
scopeId = getResourceRequestScopeId(extCtx, request);
doFacesRender(request, response, context, lifecycle, scopeId, preExistingAttributes);
}
catch (Exception e)
{
if (!mDebugLoggingEnabled.booleanValue())
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);
}
}
private boolean isFacesResourceRequest(ResourceRequest request)
{
return (request.getParameter(PortletExternalContextImpl.JSF_TARGET_VIEWID_RENDER_PARAMETER) != null ||
request.getParameter(Bridge.FACES_VIEW_ID_PARAMETER) != null ||
request.getParameter(Bridge.FACES_VIEW_PATH_PARAMETER) != null);
}
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 getResourceRequestScopeId(ExternalContext extCtx, PortletRequest request)
{
// get the render scope this resource request is contained in
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
return initBridgeRequestScope(request, null);
}
// Check to see if this resource request is targeting the same view or a different one
Map<String, Object> m = getScopeMap(scopeId);
Map<String, String> childResourceScopeMap = (Map<String, String>) m.get(CHILD_RESOURCE_REQUEST_SCOPE_MAP);
String scopeIdKey = (String) m.get(SCOPE_VIEW_KEY);
String childIdKey = getScopeViewKey(extCtx);
// If the resource is a PPR it will target the same view as the render/parent
// In which case use the same scope
if (scopeIdKey != null && scopeIdKey.equalsIgnoreCase(childIdKey))
{
return scopeId;
}
else
{
String childScopeId = null;
if (childResourceScopeMap != null)
{
childScopeId = childResourceScopeMap.get(childIdKey);
}
if (childScopeId == null)
{
childScopeId = createBridgeRequestScope(request);
if (childResourceScopeMap == null)
{
childResourceScopeMap = (Map<String, String>) new HashMap(3);
m.put(CHILD_RESOURCE_REQUEST_SCOPE_MAP, childResourceScopeMap);
}
childResourceScopeMap.put(childIdKey, childScopeId);
}
return childScopeId;
}
}
private String getScopeViewKey(ExternalContext extCtx)
{
String scopeIdKey = null;
String pathInfo = extCtx.getRequestPathInfo();
String servletPath = extCtx.getRequestServletPath();
if (pathInfo == null) scopeIdKey = servletPath;
else if (servletPath == null) scopeIdKey = pathInfo;
else scopeIdKey = servletPath + pathInfo;
return scopeIdKey;
}
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 createBridgeRequestScope(PortletRequest request)
{
// 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();
return qualifyScopeId(mPortletConfig.getPortletName(),
request.getPortletSession(true).getId(),
request.getPortletMode().toString(),
uid.toString());
}
private String initBridgeRequestScope(PortletRequest request, StateAwareResponse response)
{
String requestScopeId = createBridgeRequestScope(request);
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 Map<String, Object> getScopeMap(String scopeId)
{
PortletContext portletContext = mPortletConfig.getPortletContext();
// 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 null;
}
return requestScopeMap.get(scopeId);
}
}
@SuppressWarnings("unchecked")
private boolean restoreBridgeRequestScopeData(PortletRequest request, String scopeId)
throws BridgeException
{
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;
}
m = getScopeMap(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)
{
// Scopes can now contain a Map to child scopes -- first check to see if this scope
// contains such a Map -- if so them remove these scopes too.
Map<String, String> childMap = (Map<String, String>) scope.get(CHILD_RESOURCE_REQUEST_SCOPE_MAP);
if (childMap != null)
{
Set<Map.Entry<String, String>> s = childMap.entrySet();
Iterator<Map.Entry<String, String>> i = s.iterator();
while (i.hasNext())
{
// the value of each entry is the scope to remove
removeRequestScopes(i.next().getValue());
}
}
// Now do the notify
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)
{
Vector<String> scopesToRemove = (Vector<String>) new Vector(5);
Iterator<String> iterator = requestScopeMap.keySet().iterator();
while (iterator.hasNext())
{
String scopeId = iterator.next();
if (scopeId != null && scopeId.startsWith(scopePrefix))
{
// don't remove from iterator -- rather directly from the Map
// So the Maps.remove is called which invoked notifyPredestroy()
scopesToRemove.add(scopeId);
}
}
iterator = scopesToRemove.iterator();
while (iterator.hasNext())
{
String scopeId = iterator.next();
if (scopeId != null)
{
requestScopeMap.remove(scopeId);
}
}
}
}
}
private void processFacesConfiguration(PortletContext context)
{
FacesConfigurationProcessor processor = new FacesConfigurationProcessor(context);
// Get the public render parameter mappings
mPublicParameterMappings = processor.getPublicParameterMappings();
// Get the write-behind response wrapper mappings
mWriteBehindRenderResponseWrapper = validateWrapperClass(processor.getWriteBehindRenderResponseWrapperClassName());
mWriteBehindResourceResponseWrapper = validateWrapperClass(processor.getWriteBehindResourceResponseWrapperClassName());
// Update the excluded attributes list
List<String> list = processor.getExcludedAttributes();
if (list != null)
{
ListIterator<String> i = (ListIterator<String>) list.listIterator();
while (i.hasNext())
{
String attr = i.next();
if (!mExcludedRequestAttributes.contains(attr))
{
mExcludedRequestAttributes.add(attr);
}
}
}
}
private Class<? extends BridgeWriteBehindResponse> validateWrapperClass(String className)
{
if (className == null)
{
return null;
}
try
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return (Class<? extends BridgeWriteBehindResponse>) loader.loadClass(className);
}
catch (Exception e)
{
return null;
}
}
/* 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)
{
// Don't notify PreDestroy here. New entry should be a duplicate/replacement for old entry
// containing the same elements (minus any added/removed in this request)
return super.put(key, value);
}
}
// 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);
}
}
}