The Source for Java Technology Collaboration
User: Password:



Felipe Leme's Blog

Web Applications 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?

JSP is not dead!

Posted by felipeal on July 09, 2007 at 08:35 AM | Permalink | Comments (0)

Once in a while people blog about JSP being a dead technology. Well, that's not true, it is so alive that it is even being sold in the streets of San Francisco, as shown below:

jsp-sf-small.png
I saw that place yesterday night, when I was walking home, after watching a movie. Unfortunately I didn't have a good camera with me (my cell phone does not have a flash), but thanks to Google Maps Street View I could find the place online and get a screenshot.

Notice that some parts of the picture are blurred, and I did not provide a link to Google's page. I will leave it as an exercise for the reader: find where this place is!
.
I will post the answer in another blog (along with some comments about Google and Yahoo maps), but can give some tips over the week - let's see how the comments go. Also, if you find the place, please don't reveal it (to not spoiler the quest of the others - assuming someone will have the curiosity and time to look for the answer :-) - just post a comment saying you found it, and send me the answer in private. I will publish then name of the 'winners' in the next blog....



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