/* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.myfaces.portlet.faces.context;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.render.ResponseStateManager;
import javax.portlet.ActionResponse;
import javax.portlet.BaseURL;
import javax.portlet.ClientDataRequest;
import javax.portlet.MimeResponse;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletResponse;
import javax.portlet.PortletURL;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceURL;
import javax.portlet.StateAwareResponse;
import javax.portlet.WindowState;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.BridgeDefaultViewNotSpecifiedException;
import javax.portlet.faces.BridgeInvalidViewPathException;
import org.apache.myfaces.portlet.faces.bridge.BridgeImpl;
import org.apache.myfaces.portlet.faces.util.FacesVersionUtil;
import org.apache.myfaces.portlet.faces.util.QueryString;
import org.apache.myfaces.portlet.faces.util.URLUtils;
import org.apache.myfaces.portlet.faces.util.map.EnumerationIterator;
import org.apache.myfaces.portlet.faces.util.map.PortletApplicationMap;
import org.apache.myfaces.portlet.faces.util.map.PortletInitParameterMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestHeaderMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestHeaderValuesMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestHeaders;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestParameterMap;
import org.apache.myfaces.portlet.faces.util.map.PortletRequestParameterValuesMap;
import org.apache.myfaces.portlet.faces.util.map.PortletSessionMap;
/**
* This implementation of {@link ExternalContext} is specific to the portlet implementation.
*
* Methods of interests are: - encodeActionURL - redirect
*/
public class PortletExternalContextImpl
extends ExternalContext
{
public static final String FACES_MAPPING_ATTRIBUTE =
"org.apache.myfaces.portlet.faces.context.facesMapping";
// Note: be careful -- as this attribute is prefixed to a value containg '.' it
// wouldn't be exlcuded using normal logic -- so instead BridgeImpl specially
// exlcudes/treats this package. -- i.e. all attrbiutes beginning with
// "org.apache.myfaces.portlet.faces.context." are excluded -- so beware if
// you try and add an attribute you don't want exlcuded within this package.
private static final String ENCODED_ACTION_URL_ATTRIBUTE_PREFIX =
"org.apache.myfaces.portlet.faces.context.";
public static final String RENDER_POLICY_ATTRIBUTE =
Bridge.BRIDGE_PACKAGE_PREFIX + "." + Bridge.RENDER_POLICY;
// Render parameter to store the viewId
public static final String JSF_TARGET_VIEWID_RENDER_PARAMETER = "__jpfbJSFTARGET";
public static final String JSF_RESOURCE_TARGET_VIEWID_RENDER_PARAMETER = "__jpfbJSF_RESTARGET";
public static final String NO_SCOPE = "org.apache.myfaces.portlet.faces.noScope";
public static final String SERVLET_INCLUDED_PATHINFO_ATTRIBUTE = "javax.servlet.include.path_info";
public static final String SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
private PortletContext mPortletContext;
private PortletRequest mPortletRequest;
private PortletResponse mPortletResponse;
private String mPortletName;
// External context maps
private Map<String, Object> mApplicationMap = null;
private Map<String, Object> mSessionMap = null;
private Map<String, Object> mRequestMap = null;
private Map<String, String> mRequestParameterMap = null;
private Map<String, String[]> mRequestParameterValuesMap = null;
private Map<String, String> mRequestHeaderMap = null;
private Map<String, String[]> mRequestHeaderValuesMap = null;
private Map<String, String> mInitParameterMap = null;
// maps for internal parameters (eg, those specified in query string of
// any defaultViewId) --
private Map<String, String> mTempExtraRequestParameterMap = Collections.emptyMap();
private Map<String, String[]> mTempExtraRequestParameterValuesMap = Collections.emptyMap();
private PortletRequestHeaders mPortletRequestHeaders = null;
// Requested Faces view
private String mViewId = null;
// Reverse engineered serlvet paths from mappings
private List<String> mFacesMappings = null;
private String mServletPath = null;
private String mPathInfo = null;
private String mIncludedServletPath = null;
private String mIncludedPathInfo = null;
private boolean mUseIncludeAttributeServletDependencyWorkaround;
@SuppressWarnings("unchecked")
public PortletExternalContextImpl(PortletContext portletContext, PortletRequest portletRequest,
PortletResponse portletResponse)
throws FacesException
{
mPortletContext = portletContext;
mPortletRequest = portletRequest;
mPortletResponse = portletResponse;
mPortletName = (String) mPortletRequest.getAttribute(BridgeImpl.PORTLET_NAME_ATTRIBUTE);
mFacesMappings = (List<String>) mPortletRequest.getAttribute(FACES_MAPPING_ATTRIBUTE);
setFacesVersionDependencyFlags();
// Local portals commonly use the servlet dispatcher to execute the portlet container
// A side effect of this is the javax.servlet.include path attributes are set
// Unfortunately, Faces resolves viewId targets by first looking at these attributes
// prior to consulting the externalContext (request info). To avoid Faces
// from using these bad values -- clear them by caching them (we restore on release)
mIncludedPathInfo = (String) mPortletRequest.getAttribute(SERVLET_INCLUDED_PATHINFO_ATTRIBUTE);
mIncludedServletPath = (String) mPortletRequest.getAttribute(SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE);
mPortletRequest.removeAttribute(SERVLET_INCLUDED_PATHINFO_ATTRIBUTE);
mPortletRequest.removeAttribute(SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE);
// Because determining the view accesses request parameters -- delay until its demanded
// so clients can still set request character encoding.
}
/**
* Sometimes the bridge has to workaround issues related to specific versions of a Faces
* implementation that can't be done in a generic/unobtrusive way. Here we try to determine
* which workarounds should be enabled. Currently there is only one: Faces RI (Mojarra) versions
* before 1.2_13 contained some servlet dependent code that gets hit during view resolution unless
* the bridge has set the ServletPath include attribute. Unfortunately, Liferay (5.2) portlet container
* also writes/uses this attribute.
*/
private void setFacesVersionDependencyFlags()
{
mUseIncludeAttributeServletDependencyWorkaround = true;
// First check to see if there is a web.xml init parameter setting
String disable = mPortletContext.getInitParameter("org.apache.myfaces.portlet.bridge.disableMojarraViewResolutionWorkaround");
if (disable != null)
{
mUseIncludeAttributeServletDependencyWorkaround = !Boolean.valueOf(disable).booleanValue();
}
else
{
switch (FacesVersionUtil.getFacesType())
{
case MOJARRA:
if (FacesVersionUtil.getFacesImplPatchVersion() >= 13)
{
mUseIncludeAttributeServletDependencyWorkaround = false;
}
break;
case MYFACES:
mUseIncludeAttributeServletDependencyWorkaround = false;
break;
default:
mUseIncludeAttributeServletDependencyWorkaround = true;
break;
}
}
}
public void release()
{
// Restore the included path attributes if we unset them
if (mIncludedPathInfo != null)
{
mPortletRequest.setAttribute(SERVLET_INCLUDED_PATHINFO_ATTRIBUTE, mIncludedPathInfo);
}
if (mIncludedServletPath != null)
{
mPortletRequest.setAttribute(SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE, mIncludedServletPath);
}
mPortletContext = null;
mPortletRequest = null;
mPortletResponse = null;
mApplicationMap = null;
mSessionMap = null;
mRequestMap = null;
mRequestParameterMap = null;
mRequestParameterValuesMap = null;
mRequestHeaderMap = null;
mRequestHeaderValuesMap = null;
mInitParameterMap = null;
mViewId = null;
}
/**
* This method is the gatekeeper for managing the viewId across action/render + subsequent
* renders.
*
* For the render case, when rendering the actionURL, we call this method to write the viewId in
* the interaction state when calling createActionURL() This allows us to get the viewId in action
* request. eg, /adf-faces-demo/componentDemos.jspx?_VIEW_ID=/componentDemos.jspx
*
* For the action with redirect case, we call this method when the redirect() is called and we
* encode the viewId in the navigational state so we can get the viewId in the subsequent render
* request eg, /adf-faces-demo/componentDemos.jspx?_VIEW_ID=/componentDemos.jspx
*
* We do the same as above for the action with non-redirect case as well by calling the redirect()
* method at the end of action lifecycle in ADFBridgePorttlet.process() by passing in an URL
* created by ViewHandler.getActionURL()
*
* A special case to handle direct call from the goLink/goButton component in render request (bug
* 5259313) eg, /components/goButton.jspx or http://www.oracle.com
*/
@Override
public String encodeActionURL(String url)
{
String viewId = null, path = null;
boolean nonFacesAction = false;
QueryString queryStr = null;
int queryStart = -1;
boolean isPortletURL = false;
Bridge.PortletPhase urlType = getPortletPhase();
// First check to see if the special URI indicating we should encode
// a Nonfaces target to just the current portlet (without an associated
// path based resource).
if (isPortletURL(url))
{
isPortletURL = true;
nonFacesAction = true;
//URL is of the form scheme:urlType?queryString
// remove the scheme
path = url.substring(url.indexOf(":")+ 1);
queryStart = url.indexOf('?');
if (queryStart != -1)
{
// Get the query string
queryStr = new QueryString(path.substring(queryStart + 1), "UTF8");
path = path.substring(0, queryStart);
}
if (path.equalsIgnoreCase("render"))
{
urlType = Bridge.PortletPhase.RENDER_PHASE;
}
else if (path.equalsIgnoreCase("action"))
{
urlType = Bridge.PortletPhase.ACTION_PHASE;
}
else if (path.equalsIgnoreCase("resource"))
{
urlType = Bridge.PortletPhase.RESOURCE_PHASE;
}
else
{
log("PortletExternalContextImpl.encodeActionURL: malformed portlet url "
+ url);
return url;
}
}
else if (url.startsWith("#") || isExternalURL(url) || isDirectLink(url))
{
return url;
}
else
{
// Its a Path encoded URL
// url might contain DirectLink=false parameter -- spec says remove if
// it does.
url = removeDirectLink(url);
// Now determine the target viewId
// First: split URL into path and query string
// Hold onto QueryString for later processing
queryStart = url.indexOf('?');
if (queryStart != -1)
{
// Get the query string
queryStr = new QueryString(url.substring(queryStart + 1), "UTF8");
path = url.substring(0, queryStart);
}
else
{
path = url;
// construct an empty queryString to hold the viewId
queryStr = new QueryString("UTF8");
}
// Convert relative path to context path
if (isRelativePath(path))
{
path = getPathFromRelativePath(path);
}
// Now se if this is a Faces URL
viewId = getViewIdFromPath(path);
if (viewId != null)
{
encodeFacesActionTarget(queryStr, viewId);
}
else
{
// URL points at non-Faces action
nonFacesAction = true;
encodeNonFacesActionTarget(queryStr, path);
}
}
if (getPortletPhase() == Bridge.PortletPhase.RENDER_PHASE ||
getPortletPhase() == Bridge.PortletPhase.RESOURCE_PHASE)
{ // render - write
// the viewId into
// the response
// (interaction
// state)
MimeResponse mimeResponse = (MimeResponse) getResponse();
PortletURL actionURL = null;
ResourceURL resourceURL = null;
BaseURL baseURL = null;
// Non-JSF actions are renderURLs as we merely dispatch to them
if (nonFacesAction)
{
if (isPortletURL && urlType == Bridge.PortletPhase.ACTION_PHASE)
{
baseURL = actionURL = mimeResponse.createActionURL();
}
else if (isPortletURL && urlType == Bridge.PortletPhase.RESOURCE_PHASE)
{
baseURL = resourceURL = mimeResponse.createResourceURL();
}
else
{
baseURL = actionURL = mimeResponse.createRenderURL();
}
}
else
{
baseURL = actionURL = mimeResponse.createActionURL();
}
// Add parameters so they don't get lost
Enumeration<String> list = queryStr.getParameterNames();
while (list.hasMoreElements())
{
String param = list.nextElement().toString();
if (actionURL != null && param.equals(Bridge.PORTLET_MODE_PARAMETER))
{
try
{
actionURL.setPortletMode(new PortletMode(queryStr.getParameter(param)));
}
catch (Exception e)
{
; // do nothing -- just ignore
}
}
else if (actionURL != null && param.equals(Bridge.PORTLET_WINDOWSTATE_PARAMETER))
{
try
{
actionURL.setWindowState(new WindowState(queryStr.getParameter(param)));
}
catch (Exception e)
{
; // do nothing -- just ignore
}
}
else if (param.equals(Bridge.PORTLET_SECURE_PARAMETER))
{
try
{
baseURL.setSecure(Boolean.getBoolean(queryStr.getParameter(param)));
}
catch (Exception e)
{
; // do nothing -- just ignore
}
}
else
{
baseURL.setParameter(param, queryStr.getParameter(param));
}
}
// Some portlet containers implementing wsrp choose to separate the
// consumer rewrite string that represents this URL using & as the
// spec allows for either that or &. If the container has chosen to
// to do such -- undo it (use & instead) as faces renderkits/response writers
// may reencode when writing XMl content.
url = baseURL.toString();
url = url.replaceAll("\\&\\;", "&");
}
else if (getPortletPhase() == Bridge.PortletPhase.ACTION_PHASE ||
getPortletPhase() == Bridge.PortletPhase.EVENT_PHASE)
{ // action - write the viewId to navigational state
StateAwareResponse stateResponse = (StateAwareResponse) getResponse();
// set request params into navigational states
Enumeration<String> list = queryStr.getParameterNames();
while (list.hasMoreElements())
{
String param = list.nextElement();
if (param.equals(Bridge.PORTLET_MODE_PARAMETER))
{
try
{
stateResponse.setPortletMode(new PortletMode(queryStr.getParameter(param)));
}
catch (Exception e)
{
//TODO: Ignoring is probably dangerous here as it means that we are
// EITHER using exceptions for flow control (which is extreemly
// inefficient) or we should log a message saying what the issue
// is. According to the Javadocs an exception is thrown here if the
// portlet mode is not allowed or if sendRedirect has already been
// called. In either case we should log an information type message
// here.
; // do nothing -- just ignore
}
}
else if (param.equals(Bridge.PORTLET_WINDOWSTATE_PARAMETER))
{
try
{
stateResponse.setWindowState(new WindowState(queryStr.getParameter(param)));
}
catch (Exception e)
{
; // do nothing -- just ignore
}
}
else if (param.equals(Bridge.PORTLET_SECURE_PARAMETER))
{
; // ignore -- do nothing as can't encode into an actionResponse
}
else
{
stateResponse.setRenderParameter(param, queryStr.getParameter(param));
}
}
}
// Because we want to support translating a redirect that occurs
// during a render as an in place navigation AND we can't reverse
// engineer the URL from the actionURL, we stash the queryStr on
// a request attribute, keyed with the generated URL. If this generated
// url is passed to redirect() we can get the queryStr back and
// process based on it. Do here rather then in the render if statement
// because redirect relies on this to handle non-encoded Faces URLs whether
// in an action or a render.
getRequestMap().put(ENCODED_ACTION_URL_ATTRIBUTE_PREFIX.concat(url), queryStr);
return url;
}
@Override
public void redirect(String url)
throws IOException
{
Bridge.PortletPhase phase = getPortletPhase();
switch (phase)
{
case ACTION_PHASE:
redirectDuringAction(url);
return;
case EVENT_PHASE:
redirectDuringEvent(url);
return;
case RENDER_PHASE:
redirectDuringRender(url);
return;
case RESOURCE_PHASE:
default:
return; // as per spec if a resource request its a noop as we shouldn't get here.
}
}
private void redirectDuringAction(String url)
throws IOException
{
// Distinguish between redirects to other Faces views in this app
// and everything else.
// Redirects to a view are dealt (elsewhere) as navigations --
// encode this information for later use by the bridge controller.
// Other links are redirected.
// First look to see if this is an already encoded
QueryString params = (QueryString) getRequestMap().get(ENCODED_ACTION_URL_ATTRIBUTE_PREFIX.concat(url));
if (params != null)
{
// Because we want to support translating a redirect that occurs
// during a render as an in place navigation AND we can't reverse
// engineer the URL from the actionURL, we stash the original URL on
// a request attribute, keyed with the generated URL. If this generated
// url is passed to redirect() we can get the original url back and
// process based on it.
getRequestMap().put(BridgeImpl.REDIRECT_VIEWPARAMS, params);
FacesContext.getCurrentInstance().responseComplete();
}
else if ((url.startsWith("#") || isExternalURL(url) || isDirectLink(url)))
{
((ActionResponse) getResponse()).sendRedirect(url);
FacesContext.getCurrentInstance().responseComplete();
}
else
{
// is it an unencoded Faces URL -- process it.
// recurse on redirect after calling encodeActionURL which will
// cause the attribute to be set.
redirectDuringAction(encodeActionURL(url));
}
// else nothing to do
// TODO: is there an exception to throw here?
}
private void redirectDuringEvent(String url)
throws IOException
{
redirectFacesView(url);
}
private void redirectDuringRender(String url)
throws IOException
{
redirectFacesView(url);
}
private void redirectFacesView(String url)
throws IOException
{
// Distinguish between redirects to other Faces views in this app
// and everything else.
// Redirects to a view are dealt (elsewhere) as navigations --
// encode this information for later use by the bridge controller.
// Other links are ignored.
// First look to see if this is an already encoded
QueryString params = (QueryString) getRequestMap().get(ENCODED_ACTION_URL_ATTRIBUTE_PREFIX.concat(url));
if (params != null)
{
// Only can redirect to other Faces views in an event request
if (params.getParameter(JSF_TARGET_VIEWID_RENDER_PARAMETER) != null)
{
// Because we want to support translating a redirect that occurs
// during a render as an in place navigation AND we can't reverse
// engineer the URL from the actionURL, we stash the original URL on
// a request attribute, keyed with the generated URL. If this generated
// url is passed to redirect() we can get the original url back and
// process based on it.
getRequestMap().put(BridgeImpl.REDIRECT_VIEWPARAMS, params);
FacesContext.getCurrentInstance().responseComplete();
} // else do nothing as per spec
}
else if ((url.startsWith("#") || isExternalURL(url) || isDirectLink(url)))
{
// do nothing as per spec -- can't redirect the consumer from an event
}
else
{
// is it an unencoded Faces URL -- process it.
// recurse on redirect after calling encodeActionURL which will
// cause the attribute to be set.
redirectFacesView(encodeActionURL(url));
}
// else nothing to do
// TODO: is there an exception to throw here?
}
@Override
public String encodeResourceURL(String s)
{
if (isOpaqueURL(s))
{
return s;
}
if (isExternalURL(s))
{
return encodeResourceURL(s, false);
}
else if (isViewLink(s) || isPortletURL(s))
{
return encodeResourceAsViewNavigationURL(s);
}
else if (!isInProtocolResourceLink(s))
{
return encodeResourceURL(s, true);
}
else
{
return encodePortletServedResourceURL(s);
}
}
private String encodeResourceURL(String s, boolean targetInApp)
{
if (targetInApp)
{
s = normalizeResourcePath(s);
}
// Check for backlink and viewlink markers -- if they exist replace
s = replaceResourceQueryStringMarkers(s);
// Some portlet containers implementing wsrp choose to separate the
// consumer rewrite string that represents this URL using & as the
// spec allows for either that or &. If the container has chosen to
// to do such -- undo it (use & instead) as faces renderkits/response writers
// may reencode when writing XMl content.
return mPortletResponse.encodeURL(s).replaceAll("\\&\\;", "&");
}
private String normalizeResourcePath(String s)
{
if (!s.startsWith("/"))
{
// must be a relative path -- convert it to contextPath relative
// construct our cwd (servletPath + pathInfo);
String pi = null;
String path = getRequestServletPath();
if (path == null)
{
path = getRequestPathInfo();
}
else
{
pi = getRequestPathInfo();
}
if (pi != null)
{
path = path.concat(pi);
}
// remove target
path = path.substring(0, path.lastIndexOf("/"));
s = URLUtils.convertFromRelative(path, s);
}
// prepend the context path since portletResponse.encodeURL() requires a full path URI
// Don't need to check return from getRequestContextPath because there must
// always be a vlaue even if an empty string
String ctxPath = getRequestContextPath();
if (ctxPath.length() > 0 && !s.startsWith(ctxPath))
{
s = ctxPath + s;
}
return s;
}
private String encodeResourceAsViewNavigationURL(String s)
{
// encodeActionURL takes acre of & encoding issues
return encodeActionURL(s);
}
private String encodePortletServedResourceURL(String s)
{
return encodePortletResourceURL(normalizeResourcePath(s));
}
private String encodePortletResourceURL(String url)
{
// If a Faces resource, we assume that clients have called
// ViewHandler.getActionURL() before calling encodeResourceURL when
// doing a partial page URL. Hence all other framework queryString
// params should already be encoded.
// Though might prefer to defer this analysis until the request is
// submitted this info is available within ExternalContext but not the
// bridge code -- so we would have to reestablish the faces context to
// do it then even if we subsequently don't need it because its not a
// Faces resource.
// Determine if there is a target viewId
String viewId = null, path = null;
QueryString queryStr = null;
int queryStart = -1;
// First: split URL into path and query string
// Hold onto QueryString for later processing
queryStart = url.indexOf('?');
if (queryStart != -1)
{
// Get the query string
queryStr = new QueryString(url.substring(queryStart + 1), "UTF8");
path = url.substring(0, queryStart);
}
else
{
path = url;
// construct an empty queryString to hold the viewId
queryStr = new QueryString("UTF8");
}
// Now remove up through the ContextPath as we don't want it
String ctxPath = getRequestContextPath();
int i = path.indexOf(ctxPath);
if (i != -1)
{
path = path.substring(i + ctxPath.length());
}
// Determine the viewId by inspecting the URL
// Can't be relative by the time we get here so don't check
viewId = getViewIdFromPath(path);
// TODO: Handle the case where this is a nonFaces (inprotocol) resource
// I.e. viewId is null here. Should we do something similar to
// nonFaces support from 1.0? I.e. bridge isn't expecting to handle?
if (viewId != null)
{
// This is a Faces resource
// put the viewId in the QueryStr.
queryStr.addParameter(JSF_RESOURCE_TARGET_VIEWID_RENDER_PARAMETER, viewId);
queryStr.removeParameter(Bridge.PORTLET_MODE_PARAMETER);
queryStr.removeParameter(Bridge.PORTLET_WINDOWSTATE_PARAMETER);
}
// Encode the URL
ResourceURL resource = ((MimeResponse) mPortletResponse).createResourceURL();
resource.setResourceID(path);
// Walk through the queryStr Params and add as resourceParams
// remove any attempt to set Mode/WindowState/etc. as
// not feasible here
// Add parameters so they don't get lost
Enumeration<String> list = queryStr.getParameterNames();
while (list.hasMoreElements())
{
String param = list.nextElement().toString();
if (param.equals(Bridge.PORTLET_MODE_PARAMETER))
{
// do nothing -- just ignore -- can't encode in a resourceURL
}
else if (param.equals(Bridge.PORTLET_WINDOWSTATE_PARAMETER))
{
// do nothing -- just ignore -- can't encode in a resourceURL
}
else if (param.equals(Bridge.PORTLET_SECURE_PARAMETER))
{
try
{
resource.setSecure(Boolean.getBoolean(queryStr.getParameter(param)));
}
catch (Exception e)
{
; // do nothing -- just ignore
}
}
else
{
resource.setParameter(param, queryStr.getParameter(param));
}
}
return resource.toString().replaceAll("\\&\\;", "&");
}
@Override
public void dispatch(String requestURI)
throws IOException, FacesException
{
if (requestURI == null)
{
throw new java.lang.NullPointerException();
}
PortletRequestDispatcher prd = mPortletContext.getRequestDispatcher(requestURI);
if (prd == null)
{
throw new IllegalArgumentException("No request dispatcher can be created for the specified path: " +
requestURI);
}
try
{
prd.forward((PortletRequest) getRequest(), (PortletResponse) getResponse());
}
catch (PortletException e)
{
if (e.getMessage() != null)
{
throw new FacesException(e.getMessage(), e);
}
else
{
throw new FacesException(e);
}
}
}
@Override
public Object getSession(boolean create)
{
return mPortletRequest.getPortletSession(create);
}
@Override
public Object getContext()
{
return mPortletContext;
}
@Override
public Object getRequest()
{
return mPortletRequest;
}
@Override
public Object getResponse()
{
return mPortletResponse;
}
@Override
public Map<String, Object> getApplicationMap()
{
if (mApplicationMap == null)
{
mApplicationMap = new PortletApplicationMap(mPortletContext);
}
return mApplicationMap;
}
@Override
public Map<String, Object> getSessionMap()
{
if (mSessionMap == null)
{
mSessionMap = new PortletSessionMap(mPortletRequest);
}
return mSessionMap;
}
@Override
public Map<String, Object> getRequestMap()
{
if (mRequestMap == null)
{
mRequestMap = new PortletRequestMap(mPortletRequest);
}
return mRequestMap;
}
@Override
public Map<String, String> getRequestParameterMap()
{
if (mRequestParameterMap == null)
{
// In cases where the viewId is derived from either an attribute set by the portlet
// or from the defaultView Map, the viewId can contain additional querystring parameters.
// These are held by the ExternalContext in an internal temp Map until this method
// is called.
mRequestParameterMap =
Collections.unmodifiableMap(new PortletRequestParameterMap(mPortletRequest,
mTempExtraRequestParameterMap));
// Now that it has been used/added -- clear the temp holder as its no longer needed
mTempExtraRequestParameterMap = null;
}
return mRequestParameterMap;
}
public Map<String, String[]> getRequestParameterValuesMap()
{
if (mRequestParameterValuesMap == null)
{
// In cases where the viewId is derived from either an attribute set by the portlet
// or from the defaultView Map, the viewId can contain additional querystring parameters.
// These are held by the ExternalContext in an internal temp Map until this method
// is called.
mRequestParameterValuesMap =
Collections.unmodifiableMap(new PortletRequestParameterValuesMap(mPortletRequest,
mTempExtraRequestParameterValuesMap));
// Now that it has been used/added -- clear the temp holder as its no longer needed
mTempExtraRequestParameterValuesMap = null;
}
return mRequestParameterValuesMap;
}
public Iterator<String> getRequestParameterNames()
{
//Map is unmodifiable, so the iterator will be as well
return getRequestParameterMap().keySet().iterator();
}
public Map<String, String> getRequestHeaderMap()
{
if (mRequestHeaderMap == null)
{
if (mPortletRequestHeaders == null)
{
mPortletRequestHeaders = new PortletRequestHeaders(mPortletRequest);
}
mRequestHeaderMap = new PortletRequestHeaderMap(mPortletRequestHeaders);
}
return mRequestHeaderMap;
}
@Override
public Map<String, String[]> getRequestHeaderValuesMap()
{
if (mRequestHeaderValuesMap == null)
{
if (mPortletRequestHeaders == null)
{
mPortletRequestHeaders = new PortletRequestHeaders(mPortletRequest);
}
mRequestHeaderValuesMap = new PortletRequestHeaderValuesMap(mPortletRequestHeaders);
}
return mRequestHeaderValuesMap;
}
@Override
public Map<String, Object> getRequestCookieMap()
{
Map<String, Object> dummy = Collections.emptyMap();
return dummy;
}
@Override
public Locale getRequestLocale()
{
return mPortletRequest.getLocale();
}
@Override
public String getRequestPathInfo()
{
// Delay computing the ViewId until someone needs it
if (mViewId == null)
{
determineView(true);
}
return mPathInfo;
}
@Override
public String getRequestContextPath()
{
return mPortletRequest.getContextPath();
}
@Override
public String getInitParameter(String s)
{
return mPortletContext.getInitParameter(s);
}
@Override
public Map<String, String> getInitParameterMap()
{
if (mInitParameterMap == null)
{
mInitParameterMap = new PortletInitParameterMap(mPortletContext);
}
return mInitParameterMap;
}
@SuppressWarnings("unchecked")
public Set<String> getResourcePaths(String s)
{
return mPortletContext.getResourcePaths(s);
}
public InputStream getResourceAsStream(String s)
{
return mPortletContext.getResourceAsStream(s);
}
public String encodeNamespace(String s)
{
return ((PortletResponse) mPortletResponse).getNamespace() + s;
}
@Override
public String getRequestServletPath()
{
// Delay computing the ViewId until someone needs it
if (mViewId == null)
{
determineView(true);
}
return mServletPath;
}
@Override
public String getAuthType()
{
return mPortletRequest.getAuthType();
}
@Override
public String getRemoteUser()
{
return mPortletRequest.getRemoteUser();
}
@Override
public boolean isUserInRole(String role)
{
return mPortletRequest.isUserInRole(role);
}
@Override
public Principal getUserPrincipal()
{
return mPortletRequest.getUserPrincipal();
}
@Override
public void log(String message)
{
mPortletContext.log(message);
}
@Override
public void log(String message, Throwable t)
{
mPortletContext.log(message, t);
}
@SuppressWarnings("unchecked")
@Override
public Iterator<Locale> getRequestLocales()
{
//TODO: Cache this value...
return new EnumerationIterator<Locale>(mPortletRequest.getLocales());
}
@Override
public URL getResource(String s)
throws MalformedURLException
{
return mPortletContext.getResource(s);
}
// Start of JSF 1.2 API
/**
* <p>
* Set the environment-specific request to be returned by subsequent calls to {@link #getRequest}.
* This may be used to install a wrapper for the request.
* </p>
*
* <p>
* The default implementation throws <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend this class.
* </p>
*
*
* @since 1.2
*/
@Override
public void setRequest(Object request)
{
mPortletRequest = (PortletRequest) request;
// clear out request based cached maps
mRequestMap = null;
mRequestParameterMap = null;
mRequestParameterValuesMap = null;
mRequestHeaderMap = null;
mRequestHeaderValuesMap = null;
// if already computed recalculate the view in case it has changed
// One case where it changes is when a redirect occurs
// during a render and we impl as a direct navigation
if (mViewId != null)
{
determineView(false);
}
}
/**
*
* <p>
* Overrides the name of the character encoding used in the body of this request.
* </p>
*
* <p>
* Calling this method after the request has been accessed will have no no effect, unless a
* <code>Reader</code> or <code>Stream</code> has been obtained from the request, in which
* case an <code>IllegalStateException</code> is thrown.
* </p>
*
* <p>
* <em>Servlet:</em> This must call through to the <code>javax.servlet.ServletRequest</code>
* method <code>setCharacterEncoding()</code>.
* </p>
*
* <p>
* <em>Portlet:</em> This must call through to the <code>javax.portlet.ActionRequest</code>
* method <code>setCharacterEncoding()</code>.
* </p>
*
* <p>
* The default implementation throws <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend this class.
* </p>
*
* @throws java.io.UnsupportedEncodingException
* if this is not a valid encoding
*
* @since 1.2
*
*/
@Override
public void setRequestCharacterEncoding(String encoding)
throws UnsupportedEncodingException, IllegalStateException
{
if (getPortletPhase() != null && mPortletRequest instanceof ClientDataRequest)
{
try
{
((ClientDataRequest) mPortletRequest).setCharacterEncoding(encoding);
} catch (IllegalStateException e)
{
// Swallow this exception and proceed as if a noop.
}
}
// Though can only do this operation in a PortletAction Faces impls have
// been known to cache the encoding in memory and set in situations when
// the request doesn't specify -- turns out this code can get hit during
// the bridge render phase -- so if we threw an exception Faces would break.
// Just do a noop instead as render isn't supposed to receive parameters
}
/**
*
* <p>
* Return the character encoding currently being used to interpret this request.
* </p>
*
* <p>
* <em>Servlet:</em> This must return the value returned by the
* <code>javax.servlet.ServletRequest</code> method <code>getCharacterEncoding()</code>.
* </p>
*
* <p>
* <em>Portlet:</em> This must return the value returned by the
* <code>javax.portlet.ActionRequest</code> method <code>getCharacterEncoding()</code>.
* </p>
*
* <p>
* The default implementation throws <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend this class.
* </p>
*
* @since 1.2
*
*/
@Override
public String getRequestCharacterEncoding()
{
if (getPortletPhase() != null && mPortletRequest instanceof ClientDataRequest)
{
return ((ClientDataRequest) mPortletRequest).getCharacterEncoding();
}
else
{
// other phases -- return null as per spec
return null;
}
}
/**
*
* <p>
* Return the MIME Content-Type for this request. If not available, return <code>null</code>.
* </p>
*
* <p>
* <em>Servlet:</em> This must return the value returned by the
* <code>javax.servlet.ServletRequest</code> method <code>getContentType()</code>.
* </p>
*
* <p>
* <em>Portlet:</em> This must return <code>null</code>.
* </p>
*
* NOTE: We are deviating from the javadoc based on recommendation from JSR 301 expert group
*
* <p>
* The default implementation throws <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend this class.
* </p>
*
* @since 1.2
*/
@Override
public String getRequestContentType()
{
if (getPortletPhase() != null && mPortletRequest instanceof ClientDataRequest)
{
return ((ClientDataRequest) mPortletRequest).getContentType();
}
else
{
// Other PHASEs: return null as per spec
return null;
}
}
/**
*
* <p>
* Returns the name of the character encoding (MIME charset) used for the body sent in this
* response.
* </p>
*
* <p>
* <em>Servlet:</em> This must return the value returned by the
* <code>javax.servlet.ServletResponse</code> method <code>getCharacterEncoding()</code>.
* </p>
*
* <p>
* <em>Portlet:</em> This must return <code>null</code>.
* </p>
*
* NOTE: We are deviating from the javadoc based on recommendation from JSR 301 expert group
*
* <p>
* The default implementation throws <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend this class.
* </p>
*
* @since 1.2
*/
@Override
public String getResponseCharacterEncoding()
{
if (getPortletPhase() != null && mPortletRequest instanceof MimeResponse)
{
throw new IllegalStateException("PortletExternalContextImpl.getResponseCharacterEncoding(): Response must be a MimeResponse");
}
return ((MimeResponse) mPortletResponse).getCharacterEncoding();
}
/**
*
* <p>
* Return the MIME Content-Type for this response. If not available, return <code>null</code>.
* </p>
*
* <p>
* <em>Servlet:</em> This must return the value returned by the
* <code>javax.servlet.ServletResponse</code> method <code>getContentType()</code>.
* </p>
*
* <p>
* <em>Portlet:</em> This must return <code>null</code>.
* </p>
*
* NOTE: We are deviating from the javadoc based on recommendation from JSR 301 expert group
*
* <p>
* The default implementation throws <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend this class.
* </p>
*
* @since 1.2
*/
@Override
public String getResponseContentType()
{
if (getPortletPhase() != null && mPortletRequest instanceof MimeResponse)
{
throw new IllegalStateException("PortletExternalContextImpl.getResponseContentType(): Response must be a MimeResponse");
}
return ((MimeResponse) mPortletResponse).getContentType();
}
/**
* <p>
* Set the environment-specific response to be returned by subsequent calls to
* {@link #getResponse}. This may be used to install a wrapper for the response.
* </p>
*
* <p>
* The default implementation throws <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend this class.
* </p>
*
*
* @since 1.2
*/
@Override
public void setResponse(Object response)
{
mPortletResponse = (PortletResponse) response;
}
/**
*
* <p>
* Sets the character encoding (MIME charset) of the response being sent to the client, for
* example, to UTF-8.
* </p>
*
* <p>
* <em>Servlet:</em> This must call through to the <code>javax.servlet.ServletResponse</code>
* method <code>setCharacterEncoding()</code>.
* </p>
*
* <p>
* <em>Portlet:</em> This method must take no action.
* </p>
*
* <p>
* The default implementation throws <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend this class.
* </p>
*
*
* @since 1.2
*
*/
@Override
public void setResponseCharacterEncoding(String encoding)
{
// JSR 168 has no corresponding API.
}
// End of JSF 1.2 API
private Bridge.PortletPhase getPortletPhase()
{
return (Bridge.PortletPhase) getRequestMap().get(Bridge.PORTLET_LIFECYCLE_PHASE);
}
/**
* Gets the view identifier we should use for this request.
*/
private String getViewId(boolean updateHistory)
throws BridgeDefaultViewNotSpecifiedException, BridgeInvalidViewPathException
{
boolean modeChanged = false;
String requestedMode = mPortletRequest.getPortletMode().toString();
// See if the portlet has specified the target view
String viewId = (String) mPortletRequest.getAttribute(Bridge.VIEW_ID);
String viewPath = null;
if (viewId == null)
{
viewPath = (String) mPortletRequest.getAttribute(Bridge.VIEW_PATH);
if (viewPath != null)
{
//convert the view path into a viewId
viewId = getViewIdFromPath(viewPath);
if (viewId == null)
{
throw new BridgeInvalidViewPathException("Unable to resolve faces viewId: "
+ viewPath);
}
}
}
// Normal case is its returned in the render parameter
if (viewId == null)
{
// Next Possibility is its a Resource Request
viewId = mPortletRequest.getParameter(JSF_RESOURCE_TARGET_VIEWID_RENDER_PARAMETER);
if (viewId == null)
{
// Read the target from the request parameter
viewId = mPortletRequest.getParameter(JSF_TARGET_VIEWID_RENDER_PARAMETER);
}
log("PortletExternalContextImpl.getViewId: found jsf target viewId = " + viewId);
// ViewIds stored in RenderParams are encoded with the Mode to which they apply
// Ensure current request Mode matches before using the viewId portion
if (viewId != null)
{
int i = viewId.indexOf(':');
if (i >= 0 )
{
String mode = viewId.substring(0, i);
viewId = viewId.substring(i+1);
if (!mode.equalsIgnoreCase(requestedMode))
{
modeChanged = true;
viewId = null; // didn't match so don't use it
}
}
}
}
if (viewId == null)
{
Map<String, String> m = (Map<String,String>) mPortletContext.getAttribute(
Bridge.BRIDGE_PACKAGE_PREFIX + mPortletName
+ "." + Bridge.DEFAULT_VIEWID_MAP);
viewId = m.get(requestedMode);
if (viewId == null)
{
// If no defaultview then throw an exception
throw new BridgeDefaultViewNotSpecifiedException();
}
log("PortletExternalContextImpl.getViewId: jsf target viewId not found, defaulting to: " + viewId);
}
// Some viewId may have query string, so handle that here
// (e.g., TaskFlow has the following viewId:
// /adf.task-flow?_document=/WEB-INF/task-flow.xml&_id=task1
int queryStart = viewId.indexOf('?');
QueryString queryStr = null;
if (queryStart != -1)
{
// parse the query string and add the parameters to internal maps
// delay the creation of ParameterMap and ParameterValuesMap until
// they are needed/called by the client
queryStr = new QueryString(viewId.substring(queryStart + 1), "UTF8");
// TODO: Constants
// We store these into a temporary Map until a client calls the corresponding
// ExternalContext public api to get the request parameter map(s). In those
// methods we use these temp maps to build the overall Map that is returned.
// This roundabout technique is used to delay accessing request parameters until the
// client first requests them.
mTempExtraRequestParameterMap = new HashMap<String, String>(5);
mTempExtraRequestParameterValuesMap = new HashMap<String, String[]>(5);
// Clear any existing Request ParameterMap to ensure reconstruction
// with these new extra entries.
mRequestParameterMap = null;
mRequestParameterValuesMap = null;
Enumeration<String> list = queryStr.getParameterNames();
while (list.hasMoreElements())
{
String param = list.nextElement();
mTempExtraRequestParameterMap.put(param, queryStr.getParameter(param));
// Now deal with the multiValue case
Enumeration<String> e = queryStr.getParameterValues(param);
ArrayList<String> l = new ArrayList(5);
while (e.hasMoreElements())
{
l.add(e.nextElement());
}
String[] values = new String[l.size()];
mTempExtraRequestParameterValuesMap.put(param, l.toArray(values));
}
viewId = viewId.substring(0, queryStart);
log("PortletExternalContextImpl.getViewId: special viewId: " + viewId);
}
// before returning -- update the appropriate session attr with the current
// mode/viewid info so developers can access via EL when they want to navigate
// between modes and end up in the last view of the new mode
// Note: only do in render phase becase we need to remember the
// render parameters -- during a render all parameters are render parameters
if (updateHistory && getPortletPhase() == Bridge.PortletPhase.RENDER_PHASE)
{
updateViewChainAttribute(mPortletRequest.getPortletMode().toString(), viewId, modeChanged);
}
// If the mode changes mark this so BridgeImpl (controller) can see it shouldn't
// restore/save the scope
this.getRequestMap().put(NO_SCOPE, Boolean.valueOf(modeChanged));
return viewId;
}
private void updateViewChainAttribute(String mode, String viewId, boolean modeChanged)
{
QueryString qs = new QueryString("UTF8");
// always encode the mode in the viewId as this is used for mode transitions
qs.setParameter(Bridge.PORTLET_MODE_PARAMETER, mode);
if (!modeChanged)
{
// Build a QueryString from the request's render parameters so can preserve
// with the viewId
Map m = getRequestParameterValuesMap();
if (!m.isEmpty())
{
Set <Map.Entry<String, String[]>> set = m.entrySet();
Iterator <Map.Entry<String,String[]>> i = set.iterator();
while (i.hasNext())
{
Map.Entry<String,String[]> e = i.next();
// only add if not a viewId or viewState parameter
if (!e.getKey().equals(JSF_TARGET_VIEWID_RENDER_PARAMETER)
&& !e.getKey().equals(ResponseStateManager.VIEW_STATE_PARAM))
{
for (String s : e.getValue())
{
qs.addParameter(e.getKey(), s);
}
}
}
}
}
String toAppend = qs.toString();
StringBuffer sb = new StringBuffer(viewId.length() + toAppend.length() + 1);
viewId = sb.append(viewId).append("?").append(toAppend).toString();
// Now add to the appropriate session attribute based on Mode
Map sessionMap = getSessionMap();
StringBuffer key = new StringBuffer(100);
sessionMap.put(key.append(Bridge.VIEWID_HISTORY).append('.').append(mode).toString(), viewId);
log("History for mode: " + mode + " : " + viewId);
}
private String replaceResourceQueryStringMarkers(String s)
{
String path = null;
QueryString queryStr = null;
int queryStart = -1;
// First: split URL into path and query string
// Hold onto QueryString for later processing
queryStart = s.indexOf('?');
// references aren't in the querystring so nothing to do
if (queryStart == -1)
return s;
FacesContext context = FacesContext.getCurrentInstance();
queryStr = new QueryString(s.substring(queryStart + 1), "UTF8");
path = s.substring(0, queryStart);
String backLinkParam = queryStr.getParameter(Bridge.BACK_LINK);
// Now make sure the parameters are removed
try
{
queryStr.removeParameter(Bridge.BACK_LINK);
queryStr.removeParameter(Bridge.VIEW_LINK);
}
catch (Exception e)
{
; // do nothing -- just ignore
}
// Now process the backLink Param
if (backLinkParam != null) {
try
{
// Set backlink as parameter using value as param name
queryStr.setParameter(backLinkParam,
encodeActionURL(context.getApplication().getViewHandler().getActionURL(context,
context.getViewRoot().getViewId())));
}
catch (Exception e)
{
; // do nothing -- just ignore
}
}
// Now put the string back together
String qs = queryStr.toString();
if (qs.length() > 0)
{
s = path + "?" + qs;
}
else
{
s = path;
}
return s;
}
private void mapPathsFromViewId(String viewId, List<String> mappings)
{
if (viewId == null || mappings == null)
{
// Fail safe -- even if we didn't find a servlet mapping set path
// info
// as if we did as this value is all anything generally depends on
mPathInfo = viewId;
return;
}
// The only thing that matters is we use a configured mapping
// So just use the first one
String mapping = mappings.get(0);
if (mapping.startsWith("*"))
{
// we are using suffix mapping
viewId =
viewId.substring(0, viewId.lastIndexOf('.')) + mapping.substring(mapping.indexOf('.'));
// we are extension mapped
mServletPath = viewId;
mPathInfo = null;
// Workaround Faces RI that has Servlet dependencies if this isn't set
if (mUseIncludeAttributeServletDependencyWorkaround)
mPortletRequest.setAttribute(SERVLET_INCLUDED_SERVLETPATH_ATTRIBUTE, mServletPath);
}
else
{
// we are using prefix mapping
int j = mapping.lastIndexOf("/*");
if (j != -1)
{
mServletPath = mapping.substring(0, j);
}
else
{
// is it valid to omit the trailing /*????
mServletPath = mapping;
}
// Fail safe -- even if we didn't find a servlet mapping set path info
// as if we did as this value is all anything generally depends on
mPathInfo = viewId;
}
}
private String extensionMappingFromViewId(String viewId)
{
// first remove/ignore any querystring
int i = viewId.indexOf('?');
if (i != -1)
{
viewId = viewId.substring(0, i);
}
int extLoc = viewId.lastIndexOf('.');
if (extLoc != -1 && extLoc > viewId.lastIndexOf('/'))
{
StringBuilder sb = new StringBuilder("*");
sb.append(viewId.substring(extLoc));
return sb.toString();
}
return null;
}
private String getViewIdFromPath(String url)
{
// Get a string that holds the path after the Context-Path through the
// target
// First remove the query string
int i = url.indexOf("?");
if (i != -1)
{
url = url.substring(0, i);
}
// Now remove up through the ContextPath
String ctxPath = getRequestContextPath();
i = url.indexOf(ctxPath);
if (i != -1)
{
url = url.substring(i + ctxPath.length());
}
String viewId = null;
// Okay now figure out whether this is prefix or suffixed mapped
if (isSuffixedMapped(url, mFacesMappings))
{
viewId =
viewIdFromSuffixMapping(url, mFacesMappings, mPortletContext.getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME));
}
else if (isPrefixedMapped(url, mFacesMappings))
{
viewId = viewIdFromPrefixMapping(url, mFacesMappings);
}
else
{
// Not a Faces URL
viewId = null;
}
return viewId;
}
private boolean isSuffixedMapped(String url, List<String> mappings)
{
// see if the viewId terminates with an extension
// if non-null value contains *.XXX where XXX is the extension
String ext = extensionMappingFromViewId(url);
return ext != null && mappings.contains(ext);
}
private String viewIdFromSuffixMapping(String url, List<String> mappings, String ctxDefault)
{
// replace extension with the DEFAULT_SUFFIX
if (ctxDefault == null)
{
ctxDefault = ViewHandler.DEFAULT_SUFFIX;
}
int i = url.lastIndexOf(".");
if (ctxDefault != null && i != -1)
{
if (ctxDefault.startsWith("."))
{
url = url.substring(0, i) + ctxDefault;
}
else
{
// shouldn't happen
url = url.substring(0, i) + "." + ctxDefault;
}
}
return url;
}
private boolean isPrefixedMapped(String url, List<String> mappings)
{
for (int i = 0; i < mappings.size(); i++)
{
String prefix = null;
String mapping = mappings.get(i);
if (mapping.startsWith("/"))
{
int j = mapping.lastIndexOf("/*");
if (j != -1)
{
prefix = mapping.substring(0, j);
}
}
if (prefix != null && url.startsWith(prefix))
{
return true;
}
}
return false;
}
private void determineView(boolean updateHistory)
{
// viewId is the actual context relative path to the resource
String viewId = getViewId(updateHistory);
if (mViewId != null && viewId.equals((mViewId)))
{
return; // No change
}
mViewId = viewId;
// Now reverse engineer the servlet paths from the mappings
// So Faces thinks was a client request
mapPathsFromViewId(mViewId, mFacesMappings);
// JSF RI relies on a request attribute setting to properly handle
// suffix mapping -- but because their suffix mapping code is servlet dependent
// we need to set it for them
setFacesMapping();
}
private String viewIdFromPrefixMapping(String url, List<String> mappings)
{
for (int i = 0; i < mappings.size(); i++)
{
String prefix = null;
String mapping = mappings.get(i);
if (mapping.startsWith("/"))
{
int j = mapping.lastIndexOf("/*");
if (j != -1)
{
prefix = mapping.substring(0, j);
}
}
if (prefix != null && url.startsWith(prefix))
{
return url.substring(prefix.length());
}
}
return null;
}
private void setFacesMapping()
{
String mapping = null;
String servletPath = this.getRequestServletPath();
// if PathInfo == null we are suffixed mapped
if (this.getRequestPathInfo() == null)
{
mapping = servletPath.substring(servletPath.lastIndexOf('.'));
}
else
{
mapping = servletPath;
}
// RI can only calculate mapping if a servlet request
// so set the attribute they expect with the appropriate info.
this.getRequestMap().put("com.sun.faces.INVOCATION_PATH", mapping);
}
private boolean isPortletURL(String url)
{
// Quick check for most common case
return url.toLowerCase().startsWith("portlet:");
}
private boolean isAbsoluteURL(String url)
{
// Quick check for most common case
url = url.toLowerCase();
if (url.startsWith("http:") || url.startsWith("https:"))
{
return true;
}
// now deal with other possible protocols -- find the potential scheme
int i = url.indexOf(":");
if (i == -1)
{
return false;
}
// Now make sure that the substring before the : is a valid scheme
// i.e. contains no URI reserved characters
String scheme = url.substring(0, i);
if (scheme.indexOf(";") != -1)
return false;
else if (scheme.indexOf("/") != -1)
return false;
else if (scheme.indexOf("#") != -1)
return false;
else if (scheme.indexOf("?") != -1)
return false;
else if (scheme.indexOf(" ") != -1)
return false;
else
return true;
}
private boolean isOpaqueURL(String url)
{
if (!isAbsoluteURL(url))
{
return false;
}
// Its opaque if it is absolute (contains a scheme) that isn't followed
// by a '/'
return (url.indexOf(':') != (url.indexOf('/') - 1));
}
private boolean isExternalURL(String url)
{
if (!isAbsoluteURL(url))
{
return false;
}
// otherwise see if the URL contains the ContextPath
// Simple test is that the url doesn't contain
// the CONTEXT_PATH -- though ultimately may want to test
// if we are on the same server
String ctxPath = getRequestContextPath();
int i = url.indexOf(ctxPath);
int j = url.indexOf("?");
if (i != -1 && (j == -1 || i < j))
{
return false;
}
return true;
}
private boolean isFacesURL(String url)
{
String viewId = null;
// Determine the viewId by inspecting the URL
// Convert relative path to context path
if (isRelativePath(url))
{
url = getPathFromRelativePath(url);
}
// Now se if this is a Faces URL
viewId = getViewIdFromPath(url);
return viewId != null;
}
private boolean isDirectLink(String url)
{
return isTokenLink(Bridge.DIRECT_LINK, url);
}
private boolean isInProtocolResourceLink(String url)
{
// its in protocol if either it contains the special marker or the URL
// references a Faces view/resource.
if (isTokenLink(Bridge.IN_PROTOCOL_RESOURCE_LINK, url))
{
return true;
}
else
{
return isFacesURL(url);
}
}
private boolean isViewLink(String url)
{
return isTokenLink(Bridge.VIEW_LINK, url);
}
private boolean isTokenLink(String token, String url)
{
int queryStart = url.indexOf('?');
QueryString queryStr = null;
String tokenParam = null;
if (queryStart != -1)
{
queryStr = new QueryString(url.substring(queryStart + 1), "UTF8");
tokenParam = queryStr.getParameter(token);
return Boolean.parseBoolean(tokenParam);
}
return false;
}
private String removeDirectLink(String url)
{
return removeTokenLink(Bridge.DIRECT_LINK, url);
}
private String removeInProtocolResourceLink(String url)
{
return removeTokenLink(Bridge.IN_PROTOCOL_RESOURCE_LINK, url);
}
private String removeTokenLink(String token, String url)
{
int queryStart = url.indexOf('?');
QueryString queryStr = null;
if (queryStart != -1)
{
queryStr = new QueryString(url.substring(queryStart + 1), "UTF8");
queryStr.removeParameter(token);
String query = queryStr.toString();
if (query != null && query.length() != 0)
{
url = url.substring(0, queryStart + 1) + query;
}
else
{
url = url.substring(0, queryStart);
}
}
return url;
}
private void encodeFacesActionTarget(QueryString queryStr, String target)
{
// determine mode that we need to encode with this viewId
String mode = queryStr.getParameter(Bridge.PORTLET_MODE_PARAMETER);
if (mode == null)
{
mode = mPortletRequest.getPortletMode().toString();
}
else if (!mode.equalsIgnoreCase(mPortletRequest.getPortletMode().toString()))
{
// mode changed -- scopes shouldn't be managed (saved/restored) when a mode changes
// so mark here so the BrigeImpl (controller) knows what to do.
this.getRequestMap().put(NO_SCOPE, Boolean.TRUE);
}
// put the viewId in the QueryStr.
queryStr.setParameter(JSF_TARGET_VIEWID_RENDER_PARAMETER,
new StringBuffer(100).append(mode).append(':').append(target).toString());
}
private void encodeNonFacesActionTarget(QueryString queryStr, String target)
{
// whenever navigating to nonJSF target we don't need to manage scope
this.getRequestMap().put(NO_SCOPE, Boolean.TRUE);
// put the viewId in the QueryStr.
queryStr.setParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER, target);
}
private String getPathFromRelativePath(String url)
{
String pathInfo = getRequestPathInfo();
String servletPath = getRequestServletPath();
String currentViewId = pathInfo;
// Get by combining the ServletPath with the PathIno
if (servletPath != null)
{
if (pathInfo != null)
{
currentViewId = servletPath + pathInfo;
}
else
{
currentViewId = servletPath;
}
}
// reduce to only the path portion
String prefixURL = currentViewId.substring(0, currentViewId.lastIndexOf('/'));
if (prefixURL.length() != 0 && !prefixURL.startsWith("/"))
{
// TODO: report/log an error
return null; // this shouldn't happen, if so just return
}
if (url.startsWith("./"))
{
url = url.substring(2);
}
while (url.startsWith("../") && prefixURL.length() != 0)
{
url = url.substring(3);
prefixURL = prefixURL.substring(0, prefixURL.lastIndexOf('/'));
}
return prefixURL + "/" + url;
}
private boolean isRelativePath(String url)
{
// relative path doesn't start with a '/'
if (isAbsoluteURL(url) || url.startsWith("/"))
{
return false;
}
// relative path if starts with a '.' or doesn't contain a '/'
if (url.startsWith("."))
{
return true;
}
// neither obviously a relative path or not -- now discount protocol
int i = url.indexOf("://");
if (i == -1)
{
return true;
}
// make sure : isn't in querystring
int j = url.indexOf('?');
if (j != -1 && j < i)
{
return true;
}
return false;
}
}