The Source for Java Technology Collaboration
User: Password:



Felipe Leme's Blog

March 2008 Archives


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

Posted by felipeal on March 06, 2008 at 12:21 AM | Permalink | Comments (4)

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?



Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds