Workaround for using SEAM Resource Servlet, File Upload etc
mirasrael Dec 16, 2008 10:58 AMI had problem with using RichFaces fileUpload, Seam graphicImage and AJAX components in JBoss portlal application with portlet bridge, because it not supports situations when Resource or File Upload servlet puts some things in session (while portlet context is not active and we are working with Request session) and then component or renderer trying to access this things by key, but here already is PorletSession (wrapped by portlet bridge) and component can't find stored resource and vice versa. We are puting in portlet session from component and trying to access from servlet. Another problem (it is possible a bug), when we have redirect AJAX common parameters not initialized with STATE_ID, so if we are using AJAX components staright after redirect we are getting error "view expired - session null?".
So I made workaround for this problems with additional servlet and overriding FacesContextFactory. When some component calls getRequestContextPath from Rendering Phase it receives super.getRequestContextPath() + "/stateid/" + stateId. Then we are mapping in web.xml "/stateid/*" to our servlet wich make session like portlet session and redirect request to original servlet. When we are creating ExternalContext we are disabling Portlet Session emulation to avoid conflict with double portlet prefix applying (one time when we are making portlet prefix in servlet and second time when we are making SessionWrapper in portlet bridge ServletExternalContext).
Here is a code:
Servlet:
import org.jboss.portletbridge.application.PortletStateHolder; import org.jboss.portletbridge.application.PortletViewState; import org.jboss.portletbridge.context.ServletSessionWrapper; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.*; import java.io.IOException; public class StateIdToPortletServlet extends HttpServlet { public static final String USE_PORTLET_SESSION_ATTRIBUTE = "StateIdToPortletServlet.USE_PORTLET_SESSION_ATTRIBUTE"; @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String prefix = request.getContextPath() + request.getServletPath(); if (request.getRequestURI().startsWith(prefix)) { String path = request.getRequestURI().replaceFirst(prefix, ""); int index = path.indexOf('/', 1); String stateId = null; if (index != -1) { stateId = path.substring(1, index); path = path.substring(index); } RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher(path); if (requestDispatcher != null) { if (stateId != null) { request = new HttpServletPortletRequestWrapper(request, (PortletStateHolder) getServletContext().getAttribute(PortletStateHolder.STATE_HOLDER), stateId); } requestDispatcher.forward(request, response); } } else { response.sendError(HttpServletResponse.SC_NOT_FOUND); } } public class HttpServletPortletRequestWrapper extends HttpServletRequestWrapper { private HttpSession session; private String stateId; private PortletStateHolder stateHolder; private boolean usePortletSession = true; public HttpServletPortletRequestWrapper(HttpServletRequest request, PortletStateHolder stateHolder, String stateId) { super(request); this.stateId = stateId; this.stateHolder = stateHolder; } public HttpServletRequest getWrapped() { return (HttpServletRequest) super.getRequest(); } @Override public HttpSession getSession(boolean create) { if (!usePortletSession) return super.getSession(create); if (session == null) { if (stateHolder != null) { PortletViewState windowState = stateHolder.getWindowState(stateId); if (windowState != null) { String windowId = windowState.getWindowId(); session = new ServletSessionWrapper(super.getSession(create), PortletStateHolder.WindowIDRetriver.PORTLET_SCOPE_PREFIX + windowId + "?"); } } } if (session == null) { session = super.getSession(create); } return session; } @Override public String getParameter(String name) { if (PortletStateHolder.STATE_ID_PARAMETER.equals(name)) return stateId; return super.getParameter(name); } @Override public void setAttribute(String name, Object o) { if (USE_PORTLET_SESSION_ATTRIBUTE.equals(name)) { usePortletSession = o != null && o.toString().equals("true"); } else { super.setAttribute(name, o); } } @Override public HttpSession getSession() { return this.getSession(true); } } }
FacesFactory:
import org.jboss.portletbridge.context.*; import org.jboss.portletbridge.application.PortletStateHolder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.faces.context.FacesContextFactory; import javax.faces.context.FacesContext; import javax.faces.lifecycle.Lifecycle; import javax.faces.FacesException; import javax.portlet.*; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class FacesContextFactoryImplFix extends FacesContextFactory { /** * Hold <code>FacesContextFactory</code> from default implementation. */ private FacesContextFactory defaultFacesContextFactory; private static final Log _log = LogFactory .getLog(FacesContextFactoryImpl.class); /** * Create instance of Faces context factory, based on implementation. * * @param defaultFactory - * <p/> * Factory from JSF implementation. */ public FacesContextFactoryImplFix(FacesContextFactory defaultFactory) { super(); this.defaultFacesContextFactory = defaultFactory; if (_log.isDebugEnabled()) { _log.debug("Portal - specific FacesContextFactory has initialised"); } } /* * * (non-Javadoc) * * * * @see javax.faces.context.FacesContextFactory#getFacesContext(java.lang.Object, * * java.lang.Object, java.lang.Object, javax.faces.lifecycle.Lifecycle) * */ public FacesContext getFacesContext(Object context, Object request, Object response, Lifecycle lifecycle) throws FacesException { if ((null == context) || (null == request) || (null == response) || (null == lifecycle)) { throw new NullPointerException( "One or more parameters for a faces context instantiation is null"); } AbstractExternalContext externalContext; if ((context instanceof PortletContext) && (request instanceof ActionRequest) && (response instanceof ActionResponse)) { externalContext = new PortletExternalContextImpl((PortletContext) context, (PortletRequest) request, (PortletResponse) response); if (_log.isDebugEnabled()) { _log .debug("Portal request - create portal version of the ExternalContext for action request"); } } else if ((context instanceof PortletContext) && (request instanceof RenderRequest) && (response instanceof RenderResponse)) { externalContext = new RenderPortletExternalContextImplFix((PortletContext) context, (PortletRequest) request, (PortletResponse) response); if (_log.isDebugEnabled()) { _log .debug("Portal request - create portal version of the ExternalContext for render response"); } } else if ((context instanceof ServletContext) && (request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) { // if request don't contain namespace parameter, create default context instance. HttpServletRequest servletRequest = (HttpServletRequest) request; ServletContext servletContext = (ServletContext) context; HttpServletResponse servletResponse = (HttpServletResponse) response; // TODO - setup request encoding String stateId = servletRequest .getParameter(PortletStateHolder.STATE_ID_PARAMETER); servletRequest.setAttribute(StateIdToPortletServlet.USE_PORTLET_SESSION_ATTRIBUTE, false); if (null != stateId) { externalContext = new ServletExternalContextImpl( servletContext, servletRequest, servletResponse, stateId); if (_log.isDebugEnabled()) { _log .debug("Servlet request - create HttpServlet version of the ExternalContext"); } } else { return defaultFacesContextFactory.getFacesContext(context, request, response, lifecycle); } } else { throw new FacesException( "Unsupported environment. Only servlet or portletbridge is allowed"); } return new FacesContextImpl(externalContext, lifecycle); } }
Wrapper for Portlet Session to access this as HttpSession
import javax.portlet.PortletContext; import javax.portlet.PortletSession; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionContext; import java.util.ArrayList; import java.util.Enumeration; public class HttpSessionPortletWrapper implements PortletSession, HttpSession { private PortletSession wrapped; public HttpSessionPortletWrapper(PortletSession wrapped) { this.wrapped = wrapped; } public Object getAttribute(String s) { return wrapped.getAttribute(s); } /** * @param name a string specifying the name of the object * @throws IllegalStateException if this method is called on an * invalidated session * @return the object with the specified name * @deprecated As of Version 2.2, this method is * replaced by {@link #getAttribute}. */ public Object getValue(String name) { return getAttribute(name); } public Object getAttribute(String s, int i) { return wrapped.getAttribute(s, i); } public Enumeration getAttributeNames() { return wrapped.getAttributeNames(); } /** * @throws IllegalStateException if this method is called on an * invalidated session * @return an array of <code>String</code> * objects specifying the * names of all the objects bound to * this session * @deprecated As of Version 2.2, this method is * replaced by {@link #getAttributeNames} */ public String[] getValueNames() { Enumeration attributeNames = getAttributeNames(); ArrayList<String> valueNamesList = new ArrayList<String>(); while (attributeNames.hasMoreElements()) { valueNamesList.add((String) attributeNames.nextElement()); } String[] valueNames = new String[valueNamesList.size()]; return valueNamesList.toArray(valueNames); } public Enumeration getAttributeNames(int i) { return wrapped.getAttributeNames(i); } public long getCreationTime() { return wrapped.getCreationTime(); } public String getId() { return wrapped.getId(); } public long getLastAccessedTime() { return wrapped.getLastAccessedTime(); } /** * Returns the ServletContext to which this session belongs. * * @return The ServletContext object for the web application * @since 2.3 */ public ServletContext getServletContext() { return null; } public int getMaxInactiveInterval() { return wrapped.getMaxInactiveInterval(); } /** * @deprecated As of Version 2.1, this method is * deprecated and has no replacement. * It will be removed in a future * version of the Java Servlet API. */ public HttpSessionContext getSessionContext() { return null; } public void invalidate() { wrapped.invalidate(); } public boolean isNew() { return wrapped.isNew(); } public void removeAttribute(String s) { wrapped.removeAttribute(s); } /** * @param name the name of the object to * remove from this session * @throws IllegalStateException if this method is called on an * invalidated session * @deprecated As of Version 2.2, this method is * replaced by {@link #removeAttribute} */ public void removeValue(String name) { removeAttribute(name); } public void removeAttribute(String s, int i) { wrapped.removeAttribute(s, i); } public void setAttribute(String s, Object o) { wrapped.setAttribute(s, o); } /** * @param name the name to which the object is bound; * cannot be null * @param value the object to be bound; cannot be null * @throws IllegalStateException if this method is called on an * invalidated session * @deprecated As of Version 2.2, this method is * replaced by {@link #setAttribute} */ public void putValue(String name, Object value) { setAttribute(name, value); } public void setAttribute(String s, Object o, int i) { wrapped.setAttribute(s, o, i); } public void setMaxInactiveInterval(int i) { wrapped.setMaxInactiveInterval(i); } public PortletContext getPortletContext() { return wrapped.getPortletContext(); } }
Render portlet external context implementation
import org.jboss.portletbridge.application.PortletStateHolder; import org.jboss.portletbridge.context.RenderPortletExternalContextImpl; import javax.portlet.*; import javax.servlet.http.HttpSession; public class RenderPortletExternalContextImplFix extends RenderPortletExternalContextImpl { private HttpSession session; public RenderPortletExternalContextImplFix(PortletContext context, PortletRequest request, PortletResponse response) { super(context, request, response); } @Override public String getRequestContextPath() { String portletName = windowState.getBridgeConfig().getPortletName(); String namespace = ((RenderResponse) getPortletResponse()).getNamespace(); PortletStateHolder portletStateHolder = (PortletStateHolder) getPortletContext().getAttribute(PortletStateHolder.STATE_HOLDER); String stateId = portletStateHolder.getStateId(portletName, namespace, (RenderRequest) super.getPortletRequest()); return super.getRequestContextPath() + "/stateid/" + stateId; } @Override public Object getSession(boolean create) { if (session == null) { Object parentSession = super.getSession(create); if (parentSession == null || !(parentSession instanceof PortletSession)) { return parentSession; } session = new HttpSessionPortletWrapper((PortletSession) parentSession); } return session; } }