/* * 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.bridge.context; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.el.ELContext; import javax.el.ELContextEvent; import javax.el.ELContextListener; import javax.faces.FactoryFinder; import javax.faces.application.ApplicationFactory; import javax.faces.context.FacesContext; import javax.faces.render.ResponseStateManager; import javax.portlet.PortletContext; import javax.portlet.PortletRequest; import javax.portlet.PortletResponse; import javax.portlet.PortletSession; import javax.portlet.faces.Bridge; import javax.portlet.faces.Bridge.PortletPhase; import javax.portlet.faces.BridgeDefaultViewNotSpecifiedException; import javax.portlet.faces.BridgeException; import javax.portlet.faces.BridgeInvalidViewPathException; import javax.portlet.faces.BridgeNotAFacesRequestException; import org.jboss.portletbridge.bridge.config.BridgeConfig; import org.jboss.portletbridge.bridge.factory.BridgeFactoryFinder; import org.jboss.portletbridge.bridge.factory.BridgeRequestScopeManagerFactory; import org.jboss.portletbridge.bridge.scope.BridgeRequestScope; import org.jboss.portletbridge.bridge.scope.BridgeRequestScopeManager; import org.jboss.portletbridge.bridge.scope.BridgeRequestScopeManagerImpl; import org.jboss.portletbridge.context.PortalActionURL; import org.jboss.portletbridge.el.ELContextImpl; /** * @author <a href="http://community.jboss.org/people/kenfinni">Ken Finnigan</a> */ public class BridgeContextImpl extends BridgeContext implements ELContextListener { public static final String REQUEST_SCOPE_MANAGER = BridgeRequestScopeManagerImpl.class.getName(); private PortletContext portletContext; private PortletRequest portletRequest; private PortletResponse portletResponse; private BridgeConfig bridgeConfig; private PortletPhase portletPhase; private Map<String, Object> attributes; private boolean bridgeRequestScopePreserved = true; private String savedViewStateParam; private String navigationQueryString; private String renderRedirectQueryString; private String redirectViewId; private boolean renderRedirect = false; private boolean renderRedirectOcurredAfterDispatch = false; private List<String> preExistingRequestAttributeNames; private Map<String, String[]> preservedActionParams; private boolean viewHistoryInitialized = false; public BridgeContextImpl(BridgeConfig bridgeConfig) { BridgeContext.setCurrentInstance(this); this.bridgeConfig = bridgeConfig; this.portletContext = bridgeConfig.getPortletConfig().getPortletContext(); // Add as ELContextListener to the Faces App so we can add the // portletConfig to any newly created contexts. ((ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY)).getApplication() .addELContextListener(this); } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#release() */ @Override public void release() { portletContext = null; portletRequest = null; portletResponse = null; bridgeConfig = null; portletPhase = null; attributes = null; savedViewStateParam = null; navigationQueryString = null; renderRedirectQueryString = null; redirectViewId = null; preExistingRequestAttributeNames = null; preservedActionParams = null; BridgeContext.setCurrentInstance(null); // Remove as ELContextListener from the Faces App ((ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY)).getApplication() .removeELContextListener(this); } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setPortletContext(javax.portlet.PortletContext) */ @Override public void setPortletContext(PortletContext context) { portletContext = context; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getPortletContext() */ @Override public PortletContext getPortletContext() { return portletContext; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setPortletRequest(javax.portlet.PortletRequest) */ @Override public void setPortletRequest(PortletRequest request) { portletRequest = request; if (null != getBridgeConfig()) { initViewHistory(); } } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getPortletRequest() */ @Override public PortletRequest getPortletRequest() { return portletRequest; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setPortletResponse(javax.portlet.PortletResponse) */ @Override public void setPortletResponse(PortletResponse response) { portletResponse = response; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getPortletResponse() */ @Override public PortletResponse getPortletResponse() { return portletResponse; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setPortletRequestPhase(javax.portlet.faces.Bridge.PortletPhase) */ @Override public void setPortletRequestPhase(PortletPhase phase) { portletPhase = phase; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getPortletRequestPhase() */ @Override public PortletPhase getPortletRequestPhase() { return portletPhase; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setBridgeConfig(org.jboss.portletbridge.bridge.config.BridgeConfig) */ @Override public void setBridgeConfig(BridgeConfig config) { bridgeConfig = config; if (null != getPortletRequest()) { initViewHistory(); } } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getBridgeConfig() */ @Override public BridgeConfig getBridgeConfig() { return bridgeConfig; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getAttributes() */ @Override public Map<String, Object> getAttributes() { if (null == attributes) { attributes = new HashMap<String, Object>(10); } return attributes; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setBridgeRequestScopePreserved(boolean) */ @Override public void setBridgeRequestScopePreserved(boolean preserve) { bridgeRequestScopePreserved = preserve; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#isBridgeRequestScopePreserved() */ @Override public boolean isBridgeRequestScopePreserved() { return bridgeRequestScopePreserved; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setSavedViewStateParam(java.lang.String) */ @Override public void setSavedViewStateParam(String savedViewStateParam) { this.savedViewStateParam = savedViewStateParam; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getSavedViewStateParam() */ @Override public String getSavedViewStateParam() { return savedViewStateParam; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setNavigationQueryString(java.lang.String) */ @Override public void setNavigationQueryString(String queryString) { navigationQueryString = queryString; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getNavigationalQueryString() */ @Override public String getNavigationalQueryString() { return navigationQueryString; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setRenderRedirectQueryString(java.lang.String) */ @Override public void setRenderRedirectQueryString(String queryString) { renderRedirectQueryString = queryString; setRenderRedirect(true); } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getRenderRedirectQueryString() */ @Override public String getRenderRedirectQueryString() { return renderRedirectQueryString; } @Override public String getRedirectViewId() { return redirectViewId; } @Override public void setRedirectViewId(String viewId) { redirectViewId = viewId; setRenderRedirect(true); } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setRenderRedirect(boolean) */ @Override public void setRenderRedirect(boolean redirect) { renderRedirect = redirect; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#hasRenderRedirect() */ @Override public boolean hasRenderRedirect() { return renderRedirect; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setRenderRedirectAfterDispatch(boolean) */ @Override public void setRenderRedirectAfterDispatch(boolean afterDispatch) { if (afterDispatch) { setRenderRedirect(true); } renderRedirectOcurredAfterDispatch = afterDispatch; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#hasRenderRedirectAfterDispatch() */ @Override public boolean hasRenderRedirectAfterDispatch() { return renderRedirectOcurredAfterDispatch; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setPreFacesRequestAttrNames(java.util.List) */ @Override public void setPreFacesRequestAttrNames(List<String> names) { preExistingRequestAttributeNames = null; if (null != names) { preExistingRequestAttributeNames = new ArrayList<String>(names); } } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getPreFacesRequestAttrNames() */ @Override public List<String> getPreFacesRequestAttrNames() { return preExistingRequestAttributeNames; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setPreservedActionParams(java.util.Map) */ @Override public void setPreservedActionParams(Map<String, String[]> actionParamMap) { preservedActionParams = null; if (null != actionParamMap) { preservedActionParams = new HashMap<String, String[]>(actionParamMap); } } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getPreservedActionParams() */ @Override public Map<String, String[]> getPreservedActionParams() { return preservedActionParams; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#setViewHistory(java.lang.String, java.lang.String, boolean) */ @Override public void setViewHistory(String mode, String viewId, boolean preserveRenderParams) { PortalActionURL historyViewId = null; try { historyViewId = new PortalActionURL(viewId); } catch (MalformedURLException e) { log("MalformedURL for " + viewId + " in setViewHistory()", e); } if (null != historyViewId) { historyViewId.addParameter(Bridge.PORTLET_MODE_PARAMETER, mode); if (preserveRenderParams) { // Build a QueryString from the request's render parameters so can preserve // with the viewId FacesContext facesContext = FacesContext.getCurrentInstance(); Map<String, String[]> renderParams = null; if (null != facesContext) { renderParams = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterValuesMap(); } else { renderParams = getPortletRequest().getPrivateParameterMap(); } if (!renderParams.isEmpty()) { Set<Map.Entry<String, String[]>> keys = renderParams.entrySet(); for (Entry<String, String[]> entry : keys) { if (!entry.getKey().equals(bridgeConfig.getViewIdRenderParameterName()) && !entry.getKey().equals(ResponseStateManager.VIEW_STATE_PARAM)) { for (String value : entry.getValue()) { historyViewId.addParameter(entry.getKey(), value); } } } } } String queryString = historyViewId.getQueryString(); String historyViewPath = historyViewId.getPath() + (queryString != null ? "?" + queryString : ""); getPortletRequest().getPortletSession(true).setAttribute(Bridge.VIEWID_HISTORY + "." + mode, historyViewPath); } } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getViewHistory(java.lang.String) */ @Override public String getViewHistory(String mode) { StringBuffer key = new StringBuffer(100); return (String) FacesContext.getCurrentInstance().getExternalContext().getSessionMap() .get(key.append(Bridge.VIEWID_HISTORY).append('.').append(mode).toString()); } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getFacesViewIdFromRequest(boolean) */ @Override public String getFacesViewIdFromRequest(boolean excludeQueryString) throws BridgeInvalidViewPathException { FacesContext facesContext = FacesContext.getCurrentInstance(); if (null == facesContext) { return getViewId(portletRequest, excludeQueryString); } else { return getViewId((PortletRequest) facesContext.getExternalContext().getRequest(), excludeQueryString); } } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getFacesViewId(boolean) */ @Override public String getFacesViewId(boolean excludeQueryString) throws BridgeInvalidViewPathException { String viewId = null; FacesContext facesContext = FacesContext.getCurrentInstance(); if (null != facesContext && null != facesContext.getViewRoot()) { viewId = facesContext.getViewRoot().getViewId(); } else { viewId = getFacesViewIdFromRequest(excludeQueryString); if (null == viewId) { viewId = getDefaultFacesViewIdForRequest(excludeQueryString); } } return viewId; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getBridgeScope() */ @Override public BridgeRequestScope getBridgeScope() { return getBridgeRequestScopeManager().getRequestScope(this); } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getBridgeScope(java.lang.String, java.lang.String) */ @Override public BridgeRequestScope getBridgeScope(String viewId, String mode) { return getBridgeRequestScopeManager().getRequestScope(this, viewId, mode); } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getBridgeRequestScopeManager() */ @Override public BridgeRequestScopeManager getBridgeRequestScopeManager() { PortletSession session = getPortletRequest().getPortletSession(true); BridgeRequestScopeManager scopeManager = (BridgeRequestScopeManager) session.getAttribute(REQUEST_SCOPE_MANAGER); if (null == scopeManager) { scopeManager = createBridgeRequestScopeManager(); } return scopeManager; } private synchronized BridgeRequestScopeManager createBridgeRequestScopeManager() { BridgeRequestScopeManager scopeManager = (BridgeRequestScopeManager) getPortletRequest().getPortletSession(true) .getAttribute(REQUEST_SCOPE_MANAGER); if (null == scopeManager) { getPortletRequest().getPortletSession(true).setAttribute( REQUEST_SCOPE_MANAGER, ((BridgeRequestScopeManagerFactory) BridgeFactoryFinder .getFactoryInstance(BridgeRequestScopeManagerFactory.class)) .getBridgeRequestScopeManager(getBridgeConfig())); scopeManager = (BridgeRequestScopeManager) getPortletRequest().getPortletSession(true).getAttribute( REQUEST_SCOPE_MANAGER); } return scopeManager; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getFacesViewIdFromPath(java.lang.String) */ @Override public String getFacesViewIdFromPath(String path) throws BridgeInvalidViewPathException { // First remove the query string int index = path.indexOf('?'); if (index != -1) { path = path.substring(0, index); } // Remove Session Id if present index = path.lastIndexOf(";" + bridgeConfig.getSessionIdParameterName()); if (index != -1) { path = path.substring(0, index); } // Now remove up through the ContextPath path = getPathWithoutContext(path, getPortletRequest().getContextPath()); String viewId = null; List<String> facesMappings = getBridgeConfig().getFacesServletMappings(); List<String> facesSuffixes = getBridgeConfig().getFacesSuffixes(); String prefix = getPrefix(path, facesMappings); if (isSuffixedMapped(path, facesMappings)) { viewId = viewIdFromSuffixMapping(path, facesSuffixes); } else if (null != prefix) { viewId = path.substring(prefix.length()); } else { // Not a Faces URL viewId = null; } return viewId; } private String getPathWithoutContext(String path, String contextPath) { int pos = path.indexOf(contextPath); if (pos != -1) { if (path.indexOf('/', pos + 1) > 0) { if (pos + contextPath.length() != path.length()) { path = path.substring(pos + contextPath.length()); } } } return path; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#getDefaultFacesViewIdForRequest(boolean) */ @Override public String getDefaultFacesViewIdForRequest(boolean excludeQueryString) throws BridgeDefaultViewNotSpecifiedException { PortletRequest request = null; FacesContext facesContext = FacesContext.getCurrentInstance(); if (null != facesContext) { request = (PortletRequest) facesContext.getExternalContext().getRequest(); } if (null == request) { request = getPortletRequest(); if (null == request) { throw new BridgeNotAFacesRequestException( "No PortletRequest present in Faces.getExternalContext() or BridgeContext.getPortletRequest()"); } } String viewId = getBridgeConfig().getDefaultViewMappings().get(getPortletRequest().getPortletMode().toString()); if (null == viewId) { throw new BridgeDefaultViewNotSpecifiedException("No Default View specified for portlet: " + getBridgeConfig().getPortletConfig().getPortletName()); } if (excludeQueryString) { viewId = excludeQuery(viewId); } return viewId; } /** * @see org.jboss.portletbridge.bridge.context.BridgeContext#appendClientWindowId(String) */ @Override public String appendClientWindowId(String url) { // Do Nothing for JSF 2.0 return url; } protected String excludeQuery(String viewId) { int queryStart = viewId.indexOf('?'); if (queryStart != -1) { return viewId.substring(0, queryStart); } return viewId; } /** * ELContextListener impl */ public void contextCreated(ELContextEvent ece) { // Add the portletConfig to the ELContext so it is evaluated ELContext elContext = ece.getELContext(); // FacesContext (where the Faces/Bridge ELContext is created doesn't have // access to the PortletConfig which the Bridge ELResolver needs. // The config object is added as an attribute here in the ContextListener. // However only add to a EL context created within Faces as the JSP // ELContext/Resolver will naturally resolve the config (as long as the // page devleper has used the <portlet:defineObjects> tag. // Because listeners are called at app scope we must ensure that only // the active portlet's config is added to the ELContext. To do this, check // the portletName previously stored as a request attribute against the config. // Make sure our bridge instance is handling this context FacesContext fCtx = (FacesContext) elContext.getContext(FacesContext.class); if (null == fCtx) { return; } BridgeContext bridgeContext = BridgeContext.getCurrentInstance(); if (this == bridgeContext) { ELContextImpl portletELContext; if (elContext instanceof ELContextImpl) { // Turns out that by the time my resolver is called the ELContext may // have been wrapped -- so mark here as a FacesResolver and then do a put context portletELContext = (ELContextImpl) elContext; portletELContext.setFacesResolved(true); // Put the portletConfig object into this Map portletELContext.setPortletConfig(getBridgeConfig().getPortletConfig()); } else { // create a PortletELContext to hold future resolver state and place on this context portletELContext = new ELContextImpl(elContext.getELResolver()); portletELContext.setFacesResolved(false); } elContext.putContext(ELContextImpl.class, portletELContext); } } protected void initViewHistory() { if (!viewHistoryInitialized) { Map<String, String> viewIdDefaultMap = bridgeConfig.getDefaultViewMappings(); for (String mode : viewIdDefaultMap.keySet()) { String modeView = viewIdDefaultMap.get(mode); if (null != modeView && modeView.length() > 0) { setViewHistory(mode, modeView, false); } } viewHistoryInitialized = true; } } protected 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 null != ext && mappings.contains(ext); } protected String extensionMappingFromViewId(String viewId) { // first remove/ignore any querystring int index = viewId.indexOf('?'); if (index != -1) { viewId = viewId.substring(0, index); } int extLoc = viewId.lastIndexOf('.'); if (extLoc != -1 && extLoc > viewId.lastIndexOf('/')) { StringBuilder sb = new StringBuilder("*"); sb.append(viewId.substring(extLoc)); return sb.toString(); } return null; } protected String viewIdFromSuffixMapping(String url, List<String> suffixes) { int index = url.lastIndexOf('.'); if (index != -1) { for (String suffix : suffixes) { if (suffix.startsWith(".")) { url = url.substring(0, index) + suffix; } else { // shouldn't happen url = url.substring(0, index) + "." + suffix; } // now verify if this exists String testPath = url.startsWith("/") ? url : "/" + url; try { if (portletContext.getResource(testPath) != null) { break; } } catch (MalformedURLException m) { throw new BridgeException("View Id " + testPath + " does not exist.", m); } } } return url; } protected String getPrefix(String url, List<String> mappings) { for (String mapping : mappings) { String prefix = null; if (mapping.startsWith("/")) { int index = mapping.lastIndexOf("/*"); if (index != -1) { prefix = mapping.substring(0, index); } } if (null != prefix && url.startsWith(prefix)) { return prefix; } } return null; } protected String getViewId(PortletRequest request, boolean excludeQueryString) throws BridgeDefaultViewNotSpecifiedException, BridgeInvalidViewPathException { String requestedMode = request.getPortletMode().toString(); String viewId = (String) request.getAttribute(Bridge.VIEW_ID); String viewPath = null; if (null == viewId) { viewPath = (String) request.getAttribute(Bridge.VIEW_PATH); if (null != viewPath) { // convert the view path into a viewId viewId = getFacesViewIdFromPath(viewPath); if (null == viewId) { throw new BridgeInvalidViewPathException("Unable to resolve Faces ViewId for path: " + viewPath); } } } if (null == viewId) { // Read target from request parameter if (((Bridge.PortletPhase) portletRequest.getAttribute(Bridge.PORTLET_LIFECYCLE_PHASE)) != Bridge.PortletPhase.RESOURCE_PHASE) { viewId = portletRequest.getParameter(bridgeConfig.getViewIdRenderParameterName()); } else { viewId = portletRequest.getParameter(bridgeConfig.getViewIdResourceParameterName()); } // 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)) { viewId = null; // didn't match so don't use it } } } } if (null != viewId) { int i = viewId.indexOf(':'); if (i >= 0) { return null; } } if (null != viewId && excludeQueryString) { viewId = excludeQuery(viewId); } return viewId; } }