/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.portletbridge.context; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLEncoder; 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.Map.Entry; import java.util.Set; import javax.faces.FacesException; import javax.faces.application.ResourceHandler; 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.render.ResponseStateManager; import javax.portlet.BaseURL; import javax.portlet.ClientDataRequest; import javax.portlet.PortletContext; import javax.portlet.PortletException; import javax.portlet.PortletRequest; import javax.portlet.PortletRequestDispatcher; import javax.portlet.PortletResponse; import javax.portlet.PortletSession; import javax.portlet.faces.Bridge; import javax.portlet.faces.BridgeDefaultViewNotSpecifiedException; import javax.servlet.http.Cookie; import org.jboss.portletbridge.bridge.context.BridgeContext; import org.jboss.portletbridge.bridge.controller.BridgeController; import org.jboss.portletbridge.bridge.logger.BridgeLogger; import org.jboss.portletbridge.bridge.logger.BridgeLogger.Level; import org.jboss.portletbridge.bridge.scope.BridgeRequestScope; import org.jboss.portletbridge.context.map.EnumerationIterator; /** * Version of the {@link ExternalContext} for a Portlet request. * * @author asmirnov, <a href="http://community.jboss.org/people/kenfinni">Ken Finnigan</a> */ public abstract class PortletExternalContextImpl extends AbstractExternalContext { public static final String SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path"; public static final String PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info"; public static final String ACTION_URL_DO_NOTHITG = "/JBossPortletBridge/actionUrl/do/nothing"; public static final String RESOURCE_URL_DO_NOTHITG = "/JBossPortletBridge/resourceUrl/do/nothing"; public static final String PARTIAL_URL_DO_NOTHITG = "/JBossPortletBridge/resourceUrl/do/nothing"; public static final String WSRP_REWRITE = "wsrp_rewrite"; public static final String WSRP_REWRITE_WITH_QUESTION = WSRP_REWRITE + "?"; public static final String NAMESPACE_PREFIX = "pb"; private String namespace; private String servletPath = null; private String pathInfo = null; private String servletMappingSuffix; private String defaultJsfSuffix; private String servletMappingPrefix; private String viewId; private boolean hasNavigationRedirect = false; protected final Map<String, Map<String, String[]>> encodedActionUrlParameters = new HashMap<String, Map<String, String[]>>(); private Map<String, String[]> extraRequestParameters = new HashMap<String, String[]>(); protected BridgeContext bridgeContext; protected String acceptHeader; protected String acceptLangHeader; protected String contentType; protected String contentLength; enum Scheme { action, render, resource } public PortletExternalContextImpl(PortletContext context, PortletRequest request, PortletResponse response) { super(context, request, response); bridgeContext = BridgeContext.getCurrentInstance(); if (null == bridgeContext) { throw new FacesException("No BridgeContext instance found"); } String defaultRenderKitId = bridgeContext.getBridgeConfig().getDefaultRenderKitId(); if (null != defaultRenderKitId && null == request.getParameter(ResponseStateManager.RENDER_KIT_ID_PARAM)) { extraRequestParameters.put(ResponseStateManager.RENDER_KIT_ID_PARAM, new String[] { defaultRenderKitId }); } BridgeRequestScope scope = bridgeContext.getBridgeScope(); if (null != scope) { // Restore Action Parameters @SuppressWarnings("unchecked") Map<String, String[]> params = (Map<String, String[]>) scope.get(BridgeController.ACTION_PARAMETERS); if (null != params && params.size() > 0) { extraRequestParameters.putAll(params); } } } @Override public void setResponseCharacterEncoding(String encoding) { // Do nothing } @Override public void setRequestCharacterEncoding(String encoding) throws UnsupportedEncodingException { // Do nothing } @Override public void setResponseStatus(int statusCode) { // Do nothing } @Override public void setRequest(Object request) { super.setRequest(request); if (null != viewId) { calculateViewId(); } } @Override public void redirect(String url) throws IOException { // Implemented in sub classes } @Override public String getResponseCharacterEncoding() { throw new IllegalStateException( "PortletExternalContextImpl.getResponseCharacterEncoding(): Response must be a MimeResponse"); } @Override public String getResponseContentType() { throw new IllegalStateException("PortletExternalContextImpl.getResponseContentType(): Response must be a MimeResponse"); } @Override public String getRequestContentType() { return null; } @Override public PortletContext getContext() { return (PortletContext) super.getContext(); } public PortletRequest getPortletRequest() { return (PortletRequest) super.getRequest(); } public PortletResponse getPortletResponse() { return (PortletResponse) super.getResponse(); } public String getInitParameter(String name) { return getContext().getInitParameter(name); } protected String getNamespace() { if (null == namespace) { namespace = getPortletResponse().getNamespace(); // PBR-547 - Enhance the namespace generation to support shortening it. // Check for WSRP if (namespace.startsWith(WSRP_REWRITE)) { namespace = bridgeContext.getBridgeConfig().getPortletConfig().getPortletName() + bridgeContext.getPortletContext().getPortletContextName(); } if (bridgeContext.getBridgeConfig().isComponentNamespaceShortened()) { int hash = namespace.hashCode(); // Ensure the hash is not negative if (hash < 0) { hash = hash * -1; } String hashString = Integer.toString(hash); int hashStringLen = hashString.length(); // Shorten the hash to be the last 5 chars, as this should provide uniqueness if (hashStringLen > 5) { hashString = hashString.substring(hashStringLen - 5); } namespace = hashString; } namespace = NAMESPACE_PREFIX + namespace; } return namespace; } public URL getResource(String path) throws MalformedURLException { return getContext().getResource(path); } public InputStream getResourceAsStream(String path) { return getContext().getResourceAsStream(path); } public Set<String> getResourcePaths(String path) { return getContext().getResourcePaths(path); } protected Enumeration<String> enumerateRequestParameterNames() { List<String> names = new ArrayList<String>(); Enumeration<String> paramNames = getPortletRequest().getParameterNames(); while (paramNames.hasMoreElements()) { String name = (String) paramNames.nextElement(); names.add(name); } if (!names.contains(ResponseStateManager.VIEW_STATE_PARAM)) { names.add(ResponseStateManager.VIEW_STATE_PARAM); } names.addAll(extraRequestParameters.keySet()); return Collections.enumeration(names); } protected Object getContextAttribute(String name) { return getContext().getAttribute(name); } protected Enumeration<String> getContextAttributeNames() { return getContext().getAttributeNames(); } protected Enumeration<String> getInitParametersNames() { return getContext().getInitParameterNames(); } protected Object getRequestAttribute(String name) { if (PATH_INFO_ATTRIBUTE.equals(name)) { return getRequestPathInfo(); } else if (SERVLET_PATH_ATTRIBUTE.equals(name)) { return getRequestServletPath(); } else { return getPortletRequest().getAttribute(name); } } protected Enumeration<String> getRequestAttributeNames() { return getPortletRequest().getAttributeNames(); } protected String[] getRequestParameterValues(String name) { String[] temp = getPortletRequest().getParameterValues(name); if (null == temp || temp.length == 0) { if (ResponseStateManager.VIEW_STATE_PARAM.equals(name)) { BridgeRequestScope scope = bridgeContext.getBridgeScope(); if (null != scope) { temp = new String[] { (String) scope.get(FACES_VIEW_STATE) }; } } else { temp = extraRequestParameters.get(name); } } return temp; } protected void constructAcceptLanguageHeader() { Enumeration<Locale> locales = getPortletRequest().getLocales(); StringBuilder acceptLangHeader = new StringBuilder(64); boolean found = false; while (locales.hasMoreElements()) { Locale locale = locales.nextElement(); if (locale != null) { if (found) { acceptLangHeader.append(','); } else { found = true; } String temp = locale.getLanguage(); if (temp.length() > 0) { acceptLangHeader.append(temp); temp = locale.getCountry(); if (temp.length() > 0) { acceptLangHeader.append('-'); acceptLangHeader.append(temp); } } } } this.acceptLangHeader = found ? acceptLangHeader.toString() : null; } protected void constructAcceptHeader() { Enumeration<String> contentTypes = getPortletRequest().getResponseContentTypes(); StringBuilder acceptHeader = new StringBuilder(64); boolean found = false; while (contentTypes.hasMoreElements()) { String type = contentTypes.nextElement(); if (type != null) { if (found) { acceptHeader.append(','); } else { found = true; } acceptHeader.append(type); } } this.acceptHeader = found ? acceptHeader.toString() : null; } protected void constructContentType() { StringBuilder contentTypeBuilder = new StringBuilder(64); String contentType = ((ClientDataRequest) getRequest()).getContentType(); String charset = ((ClientDataRequest) getRequest()).getCharacterEncoding(); if (null != contentType) { int index = contentType.indexOf(';'); if (index < 0) { contentTypeBuilder.append(contentType); } else { contentTypeBuilder.append(contentType, 0, index); } if (null != charset) { contentTypeBuilder.append("; charset="); contentTypeBuilder.append(charset); } this.contentType = contentTypeBuilder.toString(); } } protected void constructContentLength() { int contentLength = ((ClientDataRequest) getRequest()).getContentLength(); if (contentLength != -1) { this.contentLength = String.valueOf(contentLength); } } protected String getRequestHeader(String name) { if ("ACCEPT".equalsIgnoreCase(name)) { if (null == acceptHeader) { constructAcceptHeader(); } return acceptHeader; } if ("ACCEPT-LANGUAGE".equalsIgnoreCase(name)) { if (null == acceptLangHeader) { constructAcceptLanguageHeader(); } return acceptLangHeader; } if ("CONTENT-TYPE".equalsIgnoreCase(name)) { return null; } if ("CONTENT-LENGTH".equalsIgnoreCase(name)) { return null; } String headerValue = getPortletRequest().getProperty(name); if (null == headerValue) { // HACK - GateIn converts all request header names to the lower case. headerValue = getPortletRequest().getProperty(name.toLowerCase()); } return headerValue; } protected Enumeration<String> getRequestHeaderNames() { List<String> names = new ArrayList<String>(); Enumeration<String> propNames = getPortletRequest().getPropertyNames(); while (propNames.hasMoreElements()) { String name = (String) propNames.nextElement(); if (!"CONTENT-TYPE".equalsIgnoreCase(name) && !"CONTENT-LENGTH".equalsIgnoreCase(name)) { names.add(name); } } names.add("ACCEPT"); names.add("ACCEPT-LANGUAGE"); return Collections.enumeration(names); } protected String[] getRequestHeaderValues(String name) { if ("ACCEPT".equalsIgnoreCase(name)) { if (null == acceptHeader) { constructAcceptHeader(); } return new String[] { acceptHeader }; } if ("ACCEPT-LANGUAGE".equalsIgnoreCase(name)) { if (null == acceptLangHeader) { constructAcceptLanguageHeader(); } return new String[] { acceptLangHeader }; } if ("CONTENT-TYPE".equalsIgnoreCase(name)) { return null; } if ("CONTENT-LENGTH".equalsIgnoreCase(name)) { return null; } Enumeration<String> properties = getPortletRequest().getProperties(name); if (!properties.hasMoreElements()) { // HACK - GateIn converts all request header names to the lower case. properties = getPortletRequest().getProperties(name.toLowerCase()); } if (properties.hasMoreElements()) { List<String> values = new ArrayList<String>(); while (properties.hasMoreElements()) { String value = properties.nextElement(); values.add(value); } return (String[]) values.toArray(EMPTY_STRING_ARRAY); } else { return null; } } protected String getRequestParameter(String name) { String temp = getPortletRequest().getParameter(name); if (null == temp) { if (ResponseStateManager.VIEW_STATE_PARAM.equals(name)) { BridgeRequestScope scope = bridgeContext.getBridgeScope(); if (null != scope) { temp = (String) scope.get(FACES_VIEW_STATE); } } else { String[] tempArray = extraRequestParameters.get(name); if (null != tempArray && tempArray.length > 0) { temp = extraRequestParameters.get(name)[0]; } } } return temp; } protected Object getSessionAttribute(String name) { return getSessionAttribute(name, getScopeForName(name)); } protected int getScopeForName(String name) { return PortletSession.PORTLET_SCOPE; } protected Object getSessionAttribute(String name, int scope) { return getPortletRequest().getPortletSession(true).getAttribute(name, scope); } protected Enumeration<String> getSessionAttributeNames() { class AttributeEnumeration implements Enumeration<String> { int scope; Enumeration<String> attributes; public AttributeEnumeration() { scope = PortletSession.PORTLET_SCOPE; attributes = getSessionAttributeNames(scope); if (!attributes.hasMoreElements()) { scope = PortletSession.APPLICATION_SCOPE; attributes = getSessionAttributeNames(scope); } } public boolean hasMoreElements() { return attributes.hasMoreElements(); } public String nextElement() { final String result = attributes.nextElement(); if (!attributes.hasMoreElements() && scope == PortletSession.PORTLET_SCOPE) { scope = PortletSession.APPLICATION_SCOPE; attributes = getSessionAttributeNames(scope); } return result; } } return new AttributeEnumeration(); // return getSessionAttributeNames(PortletSession.PORTLET_SCOPE); } protected Enumeration<String> getSessionAttributeNames(int scope) { return getPortletRequest().getPortletSession(true).getAttributeNames(scope); } protected void removeContextAttribute(String name) { getContext().removeAttribute(name); } protected void removeRequestAttribute(String name) { getPortletRequest().removeAttribute(name); } protected void removeSessionAttribute(String name) { removeSessionAttribute(name, getScopeForName(name)); } protected void removeSessionAttribute(String name, int scope) { getPortletRequest().getPortletSession(true).removeAttribute(name, scope); } protected void setContextAttribute(String name, Object value) { getContext().setAttribute(name, value); } protected void setRequestAttribute(String name, Object value) { getPortletRequest().setAttribute(name, value); } protected void setSessionAttribute(String name, Object value) { setSessionAttribute(name, value, getScopeForName(name)); } protected void setSessionAttribute(String name, Object value, int scope) { getPortletRequest().getPortletSession(true).setAttribute(name, value, scope); } /** * @param url * @return */ protected String encodeURL(String url) { return getPortletResponse().encodeURL(url); } protected String replaceUrlWhitespace(String url) { return url.replace(" ", "%20"); } public String getAuthType() { return getPortletRequest().getAuthType(); } public String getRemoteUser() { String user = getPortletRequest().getRemoteUser(); if (user == null) { Principal userPrincipal = getUserPrincipal(); if (null != userPrincipal) { user = userPrincipal.getName(); } } return user; } public String getRequestContextPath() { return getPortletRequest().getContextPath(); } public Locale getRequestLocale() { return getPortletRequest().getLocale(); } public Iterator<Locale> getRequestLocales() { return new EnumerationIterator<Locale>(getPortletRequest().getLocales()); } public String getRequestPathInfo() { if (null == viewId) { calculateViewId(); } return pathInfo; } public String getRequestServletPath() { if (null == viewId) { calculateViewId(); } return servletPath; } public Object getSession(boolean create) { return getPortletRequest().getPortletSession(create); } public Principal getUserPrincipal() { return getPortletRequest().getUserPrincipal(); } public boolean isUserInRole(String role) { return getPortletRequest().isUserInRole(role); } public void log(String message) { getContext().log(message); } public void log(String message, Throwable exception) { getContext().log(message, exception); } protected void calculateViewId() { String newViewId = bridgeContext.getFacesViewIdFromRequest(false); if (null == newViewId) { newViewId = bridgeContext.getDefaultFacesViewIdForRequest(false); if (null == newViewId) { throw new BridgeDefaultViewNotSpecifiedException(); } } if (null != newViewId) { newViewId = processViewParameters(newViewId); } if (null != newViewId && newViewId.equals(viewId)) { // No ViewId change return; } viewId = newViewId; calculateServletPath(viewId, bridgeContext.getBridgeConfig().getFacesServletMappings()); } protected String processViewParameters(String newViewId) { try { PortalActionURL portalUrl = new PortalActionURL(newViewId); if (portalUrl.parametersSize() > 0) { Map<String, String[]> params = portalUrl.getParameters(); for (Entry<String, String[]> entry : params.entrySet()) { extraRequestParameters.put(entry.getKey(), entry.getValue()); } } } catch (MalformedURLException e) { // TODO Auto-generated catch block } return newViewId; } protected void calculateServletPath(String viewId, List<String> servletMappings) { if (null != servletMappings && servletMappings.size() > 0) { String mapping = servletMappings.get(0); if (mapping.startsWith("*")) { // Suffix Mapping servletMappingSuffix = mapping.substring(mapping.indexOf('.')); viewId = viewId.substring(0, viewId.lastIndexOf('.')) + servletMappingSuffix; servletPath = viewId; pathInfo = null; getPortletRequest().setAttribute(SERVLET_PATH_ATTRIBUTE, servletPath); getPortletRequest().setAttribute("com.sun.faces.INVOCATION_PATH", servletMappingSuffix); } else if (mapping.endsWith("*")) { // Prefix Mapping mapping = mapping.substring(0, mapping.length() - 1); if (mapping.endsWith("/")) { mapping = mapping.substring(0, mapping.length() - 1); } servletMappingPrefix = servletPath = mapping; pathInfo = viewId; getPortletRequest().setAttribute("com.sun.faces.INVOCATION_PATH", servletMappingSuffix); } else { servletPath = null; pathInfo = viewId; } } else { servletPath = null; pathInfo = viewId; } } /** * @param actionURL */ protected void internalRedirect(PortalActionURL actionURL) { // Detect ViewId from URL and create new view for them. String viewId = actionURL.getParameter(Bridge.FACES_VIEW_ID_PARAMETER); if (null != viewId) { bridgeContext.setRenderRedirect(true); bridgeContext.setRedirectViewId(viewId); Map<String, String[]> requestParameters = actionURL.getParameters(); if (requestParameters.size() > 0) { bridgeContext.setRenderRedirectQueryString(actionURL.getQueryString()); } } } /** * @return the servletMappingSuffix */ public String getServletMappingSuffix() { return servletMappingSuffix; } /** * @return the defaultJsfSuffix */ public String getDefaultJsfSuffix() { return defaultJsfSuffix; } /** * @return the defaultJsfPrefix */ public String getServletMappingPrefix() { return servletMappingPrefix; } protected String getViewIdFromUrl(PortalActionURL url) { String viewId; viewId = url.getParameter(Bridge.FACES_VIEW_ID_PARAMETER); if (null == viewId) { viewId = url.getPath(); if (viewId.startsWith(getRequestContextPath())) { viewId = viewId.substring(getRequestContextPath().length()); } viewId = bridgeContext.getFacesViewIdFromPath(viewId); } return viewId; } public void dispatch(String path) throws IOException { if (null == path) { throw new IllegalArgumentException("Path to new view is null"); } PortletRequestDispatcher dispatcher = getContext().getRequestDispatcher(path); if (null == dispatcher) { throw new IllegalStateException("Dispatcher for render request is not created"); } try { boolean hasRenderRedirectedAfterForward = bridgeContext.hasRenderRedirectAfterDispatch(); if (!hasRenderRedirectedAfterForward) { dispatcher.forward(getPortletRequest(), getPortletResponse()); } else { dispatcher.include(getPortletRequest(), getPortletResponse()); } } catch (PortletException e) { throw new FacesException(e); } } @Override public String getMimeType(String file) { String mimeType = getContext().getMimeType(file); if (mimeType == null) { mimeType = getFallbackMimeType(file); } return mimeType; } @Override public String getContextName() { return getContext().getPortletContextName(); } @Override public String getRealPath(String path) { return getContext().getRealPath(path); } @Override public String getRequestScheme() { return getPortletRequest().getScheme(); } @Override public String getRequestServerName() { return getPortletRequest().getServerName(); } @Override public int getRequestServerPort() { return getPortletRequest().getServerPort(); } @Override protected Object getRequestCookie(String key) { Object value = null; Cookie[] cookies = getPortletRequest().getCookies(); if (null != key && null != cookies) { for (Cookie cookie : cookies) { if (key.equalsIgnoreCase(cookie.getName())) { value = cookie; break; } } } return value; } @Override protected Enumeration<String> getRequestCookieNames() { Cookie[] cookies = getPortletRequest().getCookies(); List<String> names = null; if (null != cookies) { names = new ArrayList<String>(); for (Cookie cookie : cookies) { names.add(cookie.getName()); } } return Collections.enumeration(names); } @Override public void invalidateSession() { PortletSession session = getPortletRequest().getPortletSession(false); if (session != null) { session.invalidate(); } } @Override public boolean isSecure() { return getPortletRequest().isSecure(); } /** * @return the hasNavigationRedirect */ boolean isHasNavigationRedirect() { return hasNavigationRedirect; } /** * @param hasNavigationRedirect the hasNavigationRedirect to set */ void setHasNavigationRedirect(boolean hasNavigationRedirect) { this.hasNavigationRedirect = hasNavigationRedirect; } public String encodeActionURL(String url) { if (null == url) { getLogger().log(Level.WARNING, "Unable to encode ActionURL for url=[null]"); return null; } String actionUrl = null; Map<String, String[]> actionParameters; // Append Client Window Id if required url = bridgeContext.appendClientWindowId(url); if (url.startsWith("#")) { actionUrl = url; actionParameters = Collections.emptyMap(); } else if (url.startsWith(WSRP_REWRITE_WITH_QUESTION)) { actionUrl = url; actionParameters = Collections.emptyMap(); } else { try { boolean escapedUrl = isStrictEscaped(url); PortalActionURL portalUrl = new PortalActionURL(escapedUrl ? unescapeUrl(url) : url, escapedUrl); if (!isInContext(portalUrl)) { if ("portlet:".equals(portalUrl.getProtocol())) { /* * * The scheme "portlet:" indicates that the target of this action is the portlet itself. Though * generally used to generate links to nonFaces views in this portlet it can also be used to generate * action or render links to a Faces view (including the current view). The scheme is followed by either * the keyword action, render or resource. render indicates a portlet renderURL should be encoded[6.8]. * action indicates a portlet actionURL should be encoded[6.9]. resource indicates a portlet resourceURL * should be encoded[6.102]. Following this url type indicator is an optional query string. Parameter * value pairs in the query string are the parameters that are to be encoded into the portletURL. */ try { Scheme scheme = Scheme.valueOf(portalUrl.getPath()); actionUrl = createPortletUrl(scheme, portalUrl, escapedUrl); } catch (IllegalArgumentException e) { actionUrl = url; } } else { String directLink = portalUrl.getParameter(Bridge.DIRECT_LINK); /* * If the inputURL contains the parameter javax.portlet.faces.DirectLink (with a value of "true") return * an absolute path derived from the inputURL. Don't remove the DirectLink parameter if it exists[6.6]. * If the inputURL contains the parameter javax.portlet.faces.DirectLink and its value is false then * remove the javax.portlet.faces.DirectLink parameter and its value from the query string and continue * processing (using the next step concerning determining the target of the URL)[6.7]. */ if (null != directLink && Boolean.parseBoolean(directLink)) { // make absolute url PortletRequest request = getPortletRequest(); portalUrl.setProtocol(request.getScheme() + ":"); portalUrl.setHost("//" + request.getServerName()); portalUrl.setPort(request.getServerPort()); } /* * if the inputURL is an absolute path external to this portlet application[6.5] return the inputURL * unchanged. */ actionUrl = escapeUrl(escapedUrl, portalUrl.toString()); } } else { String directLink = portalUrl.getParameter(Bridge.DIRECT_LINK); if (null != directLink) { /* * If the inputURL contains the parameter javax.portlet.faces.DirectLink (with a value of "true") return * an absolute path derived from the inputURL. Don't remove the DirectLink parameter if it exists[6.6]. * If the inputURL contains the parameter javax.portlet.faces.DirectLink and its value is false then * remove the javax.portlet.faces.DirectLink parameter and its value from the query string and continue * processing (using the next step concerning determining the target of the URL)[6.7]. */ if (Boolean.parseBoolean(directLink)) { // make absolute url PortletRequest request = getPortletRequest(); portalUrl.setProtocol(request.getScheme() + ":"); portalUrl.setHost("//" + request.getServerName()); portalUrl.setPort(request.getServerPort()); String directUrl = portalUrl.toString(); return escapeUrl(escapedUrl, directUrl); } } if (!isInContext(portalUrl)) { /* * if the inputURL is an absolute path external to this portlet application[6.5] return the inputURL * unchanged. */ actionUrl = escapeUrl(escapedUrl, portalUrl.toString()); } else { String pathInContext = calculatePathInContext(portalUrl); if (isFacesPath(pathInContext)) { portalUrl.setParameter(Bridge.FACES_VIEW_ID_PARAMETER, bridgeContext.getFacesViewIdFromPath(pathInContext)); actionUrl = createActionUrl(portalUrl, escapedUrl); } else { // TODO cleanup portalUrl.setParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER, pathInContext); pathInContext = calculatePathInContext(portalUrl); portalUrl.setParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER, pathInContext); actionUrl = createRenderUrl(portalUrl, escapedUrl, Collections.<String, List<String>> emptyMap()); } } } actionParameters = portalUrl.getParameters(); } catch (MalformedURLException e) { actionUrl = url; actionParameters = Collections.emptyMap(); } } // Store url parameters to reuse in redirect() encodedActionUrlParameters.put(actionUrl, actionParameters); return actionUrl; } @Override public String encodePartialActionURL(String url) { if (null == url) { throw new IllegalArgumentException(); } // Append Client Window Id if required url = bridgeContext.appendClientWindowId(url); String actionUrl = url; if (!actionUrl.startsWith("#")) { try { PortalActionURL portalUrl = new PortalActionURL(url); boolean inContext = isInContext(portalUrl); if (inContext) { actionUrl = createPartialActionUrl(portalUrl); } else { return encodeURL(portalUrl.toString()); } } catch (MalformedURLException e) { throw new FacesException(e); } } return actionUrl.replaceAll("\\&\\;", "&"); } public String encodeResourceURL(String url) { try { boolean escapedUrl = isStrictEscaped(url); PortalActionURL portalUrl = new PortalActionURL(url, escapedUrl); // JSR-301 chapter 6.1.3.1 requirements: String path = portalUrl.getPath(); if (null != portalUrl.getProtocol() && "portlet:".equalsIgnoreCase(portalUrl.getProtocol())) { // Portlet Scheme URL portalUrl.removeParameter(Bridge.VIEW_LINK); encodeBackLink(portalUrl); return replaceUrlWhitespace(encodeActionURL(portalUrl.toString())); } else if (url.startsWith(WSRP_REWRITE_WITH_QUESTION)) { return url = encodeURL(url); } else if (isOpaqueURL(url)) { // Opaque URL return url; } else if (!isInContext(portalUrl)) { // Hierarchial url outside context. portalUrl.removeParameter(Bridge.VIEW_LINK); encodeBackLink(portalUrl); return replaceUrlWhitespace(encodeURL(portalUrl.toString()).replace("&", "&")); } else if ("true".equalsIgnoreCase(portalUrl.getParameter(Bridge.VIEW_LINK))) { // Hierarchical and targets a resource that is within this application portalUrl.removeParameter(Bridge.VIEW_LINK); encodeBackLink(portalUrl); return replaceUrlWhitespace(encodeActionURL(portalUrl.toString())); } else { // For resources in the portletbridge application context add // namespace as URL parameter, to restore portletbridge session. // Remove context path from resource ID. portalUrl.removeParameter(Bridge.VIEW_LINK); encodeBackLink(portalUrl); if (path.startsWith("/")) { if (null == portalUrl.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER)) { // absolute path, remove context path from ID. portalUrl.setPath(path.substring(getRequestContextPath().length())); } } else { // resolve relative URL against current view. FacesContext facesContext = FacesContext.getCurrentInstance(); UIViewRoot viewRoot = facesContext.getViewRoot(); if (null != viewRoot && null != viewRoot.getViewId() && viewRoot.getViewId().length() > 0) { String viewId = viewRoot.getViewId(); int indexOfSlash = viewId.lastIndexOf('/'); if (indexOfSlash >= 0) { portalUrl.setPath(viewId.substring(0, indexOfSlash + 1) + path); } else { portalUrl.setPath('/' + path); } } else { // No clue where we are portalUrl.setPath('/' + path); } } portalUrl.setPath(URI.create(portalUrl.getPath()).normalize().getPath()); String facesViewId = getViewIdFromUrl(portalUrl); if (null != portalUrl.getParameter(Bridge.IN_PROTOCOL_RESOURCE_LINK)) { portalUrl.removeParameter(Bridge.IN_PROTOCOL_RESOURCE_LINK); url = createResourceUrl(portalUrl, escapedUrl); } else if (portalUrl.getPath().contains(ResourceHandler.RESOURCE_IDENTIFIER)) { // It's a JSF Resource setupJSFResourceParameters(portalUrl); url = createResourceUrl(portalUrl, escapedUrl); } else if (null != facesViewId) { portalUrl.setParameter(Bridge.FACES_VIEW_ID_PARAMETER, facesViewId); url = createResourceUrl(portalUrl, escapedUrl); } else { portalUrl.setPath(getRequestContextPath() + portalUrl.getPath()); url = encodeURL(portalUrl.toString()); } } } catch (MalformedURLException e) { throw new FacesException(e); } return url; } protected void setupJSFResourceParameters(PortalActionURL portalUrl) { String path = portalUrl.getPath(); int pos = path.indexOf(ResourceHandler.RESOURCE_IDENTIFIER); if (pos >= 0) { String resourceName = path.substring(pos + 1); int slash = resourceName.indexOf('/'); if (slash >= 0) { resourceName = resourceName.substring(slash + 1); } List<String> servletMappings = BridgeContext.getCurrentInstance().getBridgeConfig().getFacesServletMappings(); //TODO Handle this nicer? Maybe cache extension mappings on startup for (String mapping : servletMappings) { if (mapping.startsWith("*.")) { mapping = mapping.substring(1); if (resourceName.endsWith(mapping)) { resourceName = resourceName.substring(0, resourceName.indexOf(mapping)); break; } } } portalUrl.setPath(null); portalUrl.addParameter(ResourceHandler.RESOURCE_IDENTIFIER.substring(1), resourceName); } } @Override public String encodeBookmarkableURL(String baseUrl, Map<String, List<String>> parameters) { if (null == baseUrl) { throw new IllegalArgumentException(); } // Append Client Window Id if required baseUrl = bridgeContext.appendClientWindowId(baseUrl); String actionUrl = baseUrl; if (!actionUrl.startsWith("#")) { try { PortalActionURL portalUrl = new PortalActionURL(baseUrl); boolean inContext = isInContext(portalUrl); if (inContext) { actionUrl = createRenderUrl(portalUrl, isStrictEscaped(baseUrl), parameters); } else { return encodeURL(portalUrl.toString()); } } catch (MalformedURLException e) { throw new FacesException(e); } } return actionUrl.replaceAll("\\&\\;", "&"); } @Override public String encodeRedirectURL(String baseUrl, Map<String, List<String>> parameters) { // Append Client Window Id if required baseUrl = bridgeContext.appendClientWindowId(baseUrl); try { PortalActionURL portalUrl = new PortalActionURL(baseUrl); if (null != parameters && !parameters.isEmpty()) { for (Entry<String, List<String>> entry : parameters.entrySet()) { for (String value : entry.getValue()) { portalUrl.addParameter(entry.getKey(), value); } } } return encodeURL(portalUrl.toString()); } catch (MalformedURLException e) { throw new FacesException(e); } } protected boolean isAbsoluteURL(String url) { url = url.toLowerCase(); if (url.startsWith("http:") || url.startsWith("https:")) { return true; } int i = url.indexOf(':'); if (i == -1) { return false; } 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; } } protected boolean isOpaqueURL(String url) { if (!isAbsoluteURL(url)) { return false; } return (!url.startsWith("portlet:") && (url.indexOf(':') != (url.indexOf('/') - 1))); } protected String unescapeUrl(String actionUrl) { // TODO - unescape query string only return actionUrl.replaceAll("\\&\\;", "&"); } protected boolean isStrictEscaped(String url) { int queryStart = url.indexOf('?'); if (queryStart < 0) { return false; } else { // %3C == < (open tag) return url.indexOf("&", queryStart) > 0 || url.indexOf("%3C", queryStart) > 0; } } protected String escapeUrl(boolean escapedUrl, String directUrl) { if (escapedUrl) { directUrl = directUrl.replaceAll("\\&", "&"); } return directUrl; } protected boolean isInContext(PortalActionURL portalUrl) { String directLink = portalUrl.getParameter(Bridge.DIRECT_LINK); if (null != directLink) { if (Boolean.parseBoolean(directLink)) { return false; } portalUrl.removeParameter(Bridge.DIRECT_LINK); } return portalUrl.isInContext(getRequestContextPath()); } protected boolean isFacesPath(String pathInContext) { List<String> mappings = bridgeContext.getBridgeConfig().getFacesServletMappings(); if (null != getServletMappingPrefix()) { boolean isPrefixMapped = pathInContext.startsWith(getServletMappingPrefix()); if (!isPrefixMapped) { for(String mapping : mappings) { if (mapping.startsWith("*.")) { // Check for Suffix Mapping isPrefixMapped = pathInContext.endsWith(mapping.substring(2)); if (isPrefixMapped) { return isPrefixMapped; } } } } return isPrefixMapped; } else if (null != getServletMappingSuffix()) { boolean isSuffixMapped = pathInContext.endsWith(getServletMappingSuffix()); if (!isSuffixMapped) { for(String mapping : mappings) { if (mapping.endsWith("*")) { // Check for Prefix Mapping mapping = mapping.substring(0, mapping.length() - 1); if (mapping.endsWith("/")) { mapping = mapping.substring(0, mapping.length() - 1); } isSuffixMapped = pathInContext.startsWith(mapping); if (isSuffixMapped) { return isSuffixMapped; } } } } return isSuffixMapped; } return true; } protected String calculatePathInContext(PortalActionURL portalURL) { String inContextPath; String path = portalURL.getPath(); if (path.startsWith("/")) { // absolute path, remove context path from ID. // TCK compliance - return the full nonfaces view prepended with context path if (portalURL.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER) != null) { return path; } inContextPath = path.substring(getRequestContextPath().length()); // Remove Session Id if present int index = inContextPath.lastIndexOf(";" + bridgeContext.getBridgeConfig().getSessionIdParameterName()); if (index != -1) { inContextPath = inContextPath.substring(0, index); } // return path; } else { // resolve relative URL aganist current view. FacesContext facesContext = FacesContext.getCurrentInstance(); UIViewRoot viewRoot = facesContext.getViewRoot(); if (null != viewRoot && null != viewRoot.getViewId() && viewRoot.getViewId().length() > 0) { String viewId = viewRoot.getViewId(); int indexOfSlash = viewId.lastIndexOf('/'); if (indexOfSlash >= 0) { inContextPath = viewId.substring(0, indexOfSlash + 1) + path; } else { inContextPath = '/' + path; } } else { // No clue where we are inContextPath = '/' + path; } } inContextPath = URI.create(inContextPath).normalize().getPath(); return inContextPath; } protected String createPortletUrl(Scheme protocol, PortalActionURL portalUrl, boolean escape) { /* * # The scheme is followed by either the keyword action, render or resource. render indicates a portlet renderURL * should be encoded[6.8]. action indicates a portlet actionURL should be encoded[6.9]. resource indicates a portlet * resourceURL should be encoded[6.102]. # Following this url type indicator is an optional query string. Parameter * value pairs in the query string are the parameters that are to be encoded into the portletURL. * * To generate a link to a Faces view, encode the view as the value of either the _jsfBridgeViewId or _jsfBridgeViewPath * parameter (depending on whether you are encoding the viewId or the viewPath). Targets of such references are run in * new empty scopes. An exception is made when the target is the current view and either of the above parameters is * included in the query string with a value of _jsfBridgeCurrentView. In this case the url is encoded with the current * render parameters and hence retains access to its state/scope. In all cases the bridge removes the above parameter(s) * from the query string before generating the encoded url. * * For a resource url, a Faces view is only encoded if one of the _jsfBridgeViewId or _jsfBridgeViewPath parameters is * included in the query string, otherwise a nonFaces resource url is generated. The _jsfBridgeCurrentView value is used * as a shortcut to indicate the resource targets the current view. */ processJsfViewParameter(portalUrl, Bridge.FACES_VIEW_ID_PARAMETER); processJsfViewParameter(portalUrl, Bridge.FACES_VIEW_PATH_PARAMETER); switch (protocol) { case action: return createActionUrl(portalUrl, escape); case resource: return createResourceUrl(portalUrl, escape); case render: return createRenderUrl(portalUrl, escape, Collections.<String, List<String>> emptyMap()); default: return portalUrl.toString(); } } protected void processJsfViewParameter(PortalActionURL portalUrl, String facesViewParameter) { if (portalUrl.hasParameter(facesViewParameter)) { if (Bridge.FACES_USE_CURRENT_VIEW_PARAMETER.equals(portalUrl.getParameter(Bridge.FACES_VIEW_ID_PARAMETER))) { portalUrl.removeParameter(Bridge.FACES_VIEW_ID_PARAMETER); if (this instanceof RenderPortletExternalContextImpl) { portalUrl.getParameters().putAll(getPortletRequest().getPrivateParameterMap()); portalUrl.getParameters().putAll(getPortletRequest().getPublicParameterMap()); } } } } protected void encodeBackLink(PortalActionURL portalUrl) { String backLink = portalUrl.getParameter(Bridge.BACK_LINK); if (null != backLink) { portalUrl.removeParameter(Bridge.BACK_LINK); FacesContext facesContext = FacesContext.getCurrentInstance(); String viewId; if (null != facesContext.getViewRoot() && null != (viewId = facesContext.getViewRoot().getViewId())) { ViewHandler viewHandler = facesContext.getApplication().getViewHandler(); String actionURL = viewHandler.getActionURL(facesContext, viewId); try { portalUrl.addParameter(backLink, URLEncoder.encode(encodeActionURL(actionURL), "UTF-8")); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } protected String encodePortletUrl(BaseURL portletURL, boolean escape) { StringWriter out = new StringWriter(); try { portletURL.write(out, escape); return encodeURL(out.toString()); } catch (IOException e) { throw new FacesException(e); } } protected BridgeLogger getLogger() { return bridgeContext.getBridgeConfig().getLogger(); } protected abstract String createRenderUrl(PortalActionURL portalUrl, boolean escape, Map<String, List<String>> parameters); protected abstract String createResourceUrl(PortalActionURL portalUrl, boolean escape); protected abstract String createPartialActionUrl(PortalActionURL portalUrl); protected abstract String createActionUrl(PortalActionURL url, boolean escape); }