Skip to main content

Spring, ICEFaces, and the dreaded thread-bound request issue

Posted by felipeal on March 6, 2008 at 12:21 AM PST

In this blog, I provide a quick solution for an issue that arises when you use ICEFaces server-push technology combined with Spring JSF integration.



Normally, I would provide a detailed context of the issue at hand (with links to relevant pages like the java.net blog entitled "Sample Application using JSF, Spring 2.5, and Java Persistence APIs", forum posts, etc), and would spend more time polishing the text (specialyty for grammar erros that mades I luke lick a idiotic :-), etc.. But, unfortunately, I'm in a rush, so I will provide just the bare details.



Long story short, a nice feature of Spring 2.5 is that you can eliminate almost completely the XML configuration hell, replacing it by annotations. And I mean not only the Spring XML files, but also faces-config.xml. Instead of defining your managed beans on faces-config.xml, you delegate the task to Spring, and your "face" is reduced to:

<faces-config>   
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
  </application>
</faces-config>

This setup works fine with "normal" requests served by JSF pages, but if you use ICEFaces, once it pushes an event to your page, you get a nasty exception similar to this:

Mar 5, 2008 11:51:14 PM com.sun.faces.lifecycle.LifecycleImpl phase
WARNING: executePhase(RENDER_RESPONSE 6,com.icesoft.faces.context.BridgeFacesContext@9b774e) threw exception
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myBean': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request? If you are actually operating within a web request and still receive this message,your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:293)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:888)
at org.springframework.web.jsf.el.SpringBeanFacesELResolver.getValue(SpringBeanFacesELResolver.java:94)
at javax.el.CompositeELResolver.getValue(CompositeELResolver.java:53)
at com.sun.faces.el.FacesCompositeELResolver.getValue(FacesCompositeELResolver.java:58)
at org.apache.el.parser.AstIdentifier.getValue(AstIdentifier.java:45)
at org.apache.el.parser.AstValue.getValue(AstValue.java:86)
at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:186)
at com.sun.faces.application.ValueBindingValueExpressionAdapter.getValue(ValueBindingValueExpressionAdapter.java:95)
at com.icesoft.faces.component.tree.TreeRenderer.encodeBegin(TreeRenderer.java:142)
at javax.faces.component.UIComponentBase.encodeBegin(UIComponentBase.java:809)
at com.icesoft.faces.application.D2DViewHandler.renderResponse(D2DViewHandler.java:536)
at com.icesoft.faces.application.D2DViewHandler.renderResponse(D2DViewHandler.java:543)
at com.icesoft.faces.application.D2DViewHandler.renderResponse(D2DViewHandler.java:543)
at com.icesoft.faces.application.D2DViewHandler.renderResponse(D2DViewHandler.java:543)
at com.icesoft.faces.application.D2DViewHandler.renderResponse(D2DViewHandler.java:543)
at com.icesoft.faces.application.D2DViewHandler.renderResponse(D2DViewHandler.java:519)
at com.icesoft.faces.application.D2DViewHandler.renderView(D2DViewHandler.java:161)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:108)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:266)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:159)
at com.icesoft.faces.webapp.xmlhttp.PersistentFacesState.render(PersistentFacesState.java:152)

I googled for it and found some developers complaining about the same issue, but didn't find any solution. After some further research and debugging, I realized what the problem is. Normally, Spring intercepts each request (through the org.springframework.web.context.request.RequestContextListener, which must be added to web.xml), and add its BeanFactory to the request thread context (using a ThreadLocal reference). But when ICEFaces does a server push, RequestContextListener interception is not triggered.



The solution? First, adding a JSF phase listener that mimics RequestContextListener behavior:

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import static org.springframework.web.context.request.RequestContextHolder.*;

/**
* This class is necessary to set the Spring context on ICEFaces server pushes.
*
* @author felipeal
*
*/
public class SpringICEFacesIntegrationPhaseListener implements PhaseListener {

  public void afterPhase(PhaseEvent event) {
    // do nothing
  }

  public void beforePhase(PhaseEvent event) {
    if ( getRequestAttributes() == null ) {
      final FacesContext context = event.getFacesContext();
      final HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
      final ServletRequestAttributes attributes = new ServletRequestAttributes(request);
      LocaleContextHolder.setLocale(request.getLocale());
      RequestContextHolder.setRequestAttributes(attributes);
    }
  }

  public PhaseId getPhaseId() {
    return PhaseId.RENDER_RESPONSE;
  }

  /**
    * This method must be explicitiy called by Managed Beans after they
    * rendered ICEFaces stuff.
    */
  public static void releaseContext() {
    if ( getRequestAttributes() != null ) {
      RequestContextHolder.setRequestAttributes(null);
      LocaleContextHolder.setLocale(null);
    }
  }

}

Then on your managed bean, clean up the context after ICEFaces rendered the page. For instance:

protected class AbstractManagedBean {

  private PersistentFacesState persistentFacesState = null;

  public AbstractManagedBean {
    persistentFacesState = PersistentFacesState.getInstance();
  }
 
  /**
   * Initiates server-push
   */
  protected void render() throws RenderingException {
      try {
        persistentFacesState.render();
      } finally {
        SpringICEFacesIntegrationPhaseListener.releaseContext();
      }
  }
}

And the final step, adding the listener to faces-config.xml:

<faces-config>
  <lifecycle>   
<phase-listener>SpringICEFacesIntegrationPhaseListener</phase-listener>
  </lifecycle>
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
  </application>
</faces-config>

The solution is relatively simple and straightforward; hopefully this blog will help others facing the same issue.



On a side note, it would be nice to add a test case for such class, something like this:

public class SpringICEFacesIntegrationPhaseListenerTest {
 
  private final SpringICEFacesIntegrationPhaseListener listener = new SpringICEFacesIntegrationPhaseListener();
 
  public void testBeforePhase() {
    // set mock context
  final PhaseEvent event = new PhaseEvent(context,PhaseId.RENDER_RESPONSE,lifecycle);
  assertNull( "context was already set", getRequestAttributes() );
    final PhaseEvent event = new PhaseEvent(context,PhaseId.RENDER_RESPONSE,lifecycle);
    assertNull( "context was already set", getRequestAttributes() );
    listener.beforePhase(event);
    assertNotNull( "listener did not set context", getRequestAttributes() );
  }

  public void testCalledOnce() {
    // set mock context
    assertNull( "context should be set before", getRequestAttributes() );
    final PhaseEvent event = new PhaseEvent(context,PhaseId.RENDER_RESPONSE,lifecycle);
    listener.beforePhase(event);
    // verify mocks to assert listener didn't call methods
  }
}

The problem is, setting a mock FacesContext is not trivial. Initially, I thought I could use Easymock, but it would require half-a-dozen mocks, and I wasn't in the mood for that. Then I tried to use Spring Mock, but looks like it doesn't provide a MockFacesContext anymore. Other options might be Shale or JSFUnit, but I haven't had the time to play with these tools yet.



So, I'd like to finish this blog with a question - which tool/framework would you recommend for such test case?

Related Topics >>

Comments

Hi there, Thanks for pointing it, that was a quick/dirty hack. Anyways, more recent ICEFaces versions (at least 1.7.2) have already fixed this problem, so you don't need such workaround anymore. -- Felipe

Hmm apparently it works on jetty, not on Websphere 6.1...

Thanks great stuff! one little thing though... By doing: final ServletRequestAttributes attributes = new ServletRequestAttributes(request); You lose authorization information, which in our case made our page render partially. We changed it to: final ServletRequestAttributes attributes = new ServletWebRequest(request); Which also exposed user authorization. I'm not sure about the impact but for us it works now!

Ted,
An out-of-the-box support by either ICEFaces and/or Spring would be great, but I don't think ICEFaces should set these ThreadLocals directly, it would be a hack suited to a specific integration (Spring), not to mention the classes dependencies.

A more generic approach would be making sure the request listeners registered in the web application have their requestInitialized()/requestDestroyed() called at the proper times when ICEFaces do a server push. This solution not only wouldn't introduce any Spring dependency, but would avoid other frameworks (or even custom RequestListeners) face this same issue.

-- Felipe

Thanks Felipe, this is very useful information. We'll look at setting the ThreadLocal automatically when ICEfaces detects a Spring environment.

Yes, it does, good catch. I updated the blog.

Thanks for pointing it out...

-- Felipe

Should you clear the ThreadLocal's at some point by doing, LocaleContextHolder.setLocale(null); RequestContextHolder.setRequestAttributes(null);