Showing posts with label JSF. Show all posts
Showing posts with label JSF. Show all posts

Monday, February 15, 2010

An Ajax4JSF bug

The other day I had this exception:

15:16:47,296 ERROR [[Faces Servlet]] Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
 at org.ajax4jsf.org.w3c.tidy.Node.trimInitialSpace(Node.java:946)
 at org.ajax4jsf.org.w3c.tidy.Node.trimSpaces(Node.java:1012)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseInline.parse(ParserImpl.java:1125)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseRowGroup.parse(ParserImpl.java:2809)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseTableTag.parse(ParserImpl.java:2629)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseInline.parse(ParserImpl.java:1587)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseBody.parse(ParserImpl.java:978)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseTag(ParserImpl.java:203)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl$ParseHTML.parse(ParserImpl.java:486)
 at org.ajax4jsf.org.w3c.tidy.ParserImpl.parseDocument(ParserImpl.java:3409)
 at org.ajax4jsf.org.w3c.tidy.Tidy.parse(Tidy.java:363)
 at org.ajax4jsf.org.w3c.tidy.Tidy.parse(Tidy.java:261)
 at org.ajax4jsf.org.w3c.tidy.Tidy.parseDOM(Tidy.java:604)
 at org.ajax4jsf.webapp.tidy.TidyParser.parseHtmlByTidy(TidyParser.java:182)
 at org.ajax4jsf.webapp.tidy.TidyParser.parseHtml(TidyParser.java:265)
 at org.ajax4jsf.webapp.FilterServletResponseWrapper.parseContent(FilterServletResponseWrapper.java:594)
 at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:367)
 at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
 at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
 at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
 at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
 at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
 at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
 at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
 at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
 at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
 at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
 at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
 at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
 at java.lang.Thread.run(Unknown Source)

I was using JSF 1.2_13-b01-FCS with RichFaces 3.3.2.SR1. The problem turned out to be with the generated HTML content which Ajax4JSF was sending as a response. We were using a4j:repeat to generate a HTML table, and one row of this table was shown using an AJAX request. However, the default rendering behaviour of a4j:outputPanel is the using of some element (span or div, I don't remember which is the actual default), and the html tidy code above had problems with handling those elements between table rows. You just have to define layout="none" for the outputPanel, then it works fine.

The bug is since then reported: https://jira.jboss.org/jira/browse/AJSF-158

Tuesday, February 9, 2010

Using action bindings in facelets template components

When creating Facelets template components, you might face difficulties when trying to pass action bindings as component or template parameters. The problem is that the method binding gets evaluated at the place it is used, so if you write:

  <my:actionComponent action="#{backing.method}"/>

the method is invoked and the result value will be available for named 'action' inside the template. To change this behaviour, I created a method in a backing named 'ELEvaluator':

 public void evaluateMethodBinding(String el) {
  ValueExpression ve = FacesContext.getCurrentInstance().getApplication().getExpressionFactory()
   .createValueExpression(FacesContext.getCurrentInstance().getELContext(), "#{" + el + "}", Object.class);
  ve.getValue(FacesContext.getCurrentInstance().getELContext());
 }

Inside the template component I use:

    action="#{ELEvaluator.evaluateMethodBinding(action)}" 

And when passing the action parameter, I have to pass a normal String, without the '#{}' stuff:

 <my:actionComponent [...] action="backing.method(params)"/>

See also: http://seamframework.org/Community/FaceletsParamForActionMethod

Tuesday, May 12, 2009

Showing not shown Faces Messages

Most of us using JSF 1.2 RI are familiar with the warning message in our logs:

[lifecycle] WARNING: FacesMessage(s) have been enqueued, but may not have been displayed.

JavaServer Faces has a mechanism to assign messages to specific components. In the common case, if an input field has a validation error, the error message is queued to that field, and a h:message put next to the input field can show the message on the page:

<h:inputtext id="firstName"/>
<h:message for="firstName"/>

The JSF Reference Implementation has a solution to help us find out if we forgot to put a h:message tag for some components. Unfortunately, it only shows a warning message in the log, and we have no programmatic way to process those messages. Well, almost no way.

If you dare take a look in com.sun.faces.lifecycle.RenderResponsePhase, you can see that if there are any messages queued, a special variable of the Set is added to the request map. All client ids of controls with faces messages are added to this set. When h:message tags render, they access the messages queued for their associated client ids using the getMessages(String clientId) method in com.sun.faces.context.FacesContextImpl. This method removes the client id from the set, thus signalling that the messages for that client id are displayed. When finished rendering, messages for the client ids remaining in the Set are those that are not displayed. The class RenderResponsePhase simply logs these messages and then disposes the Set.

In one of my applications, I wanted to present these messages at the top of my forms. This can come really useful especially in development phase. As seen from the above, I have the necessary information ready at the very end of each render phase. I created a Seam component with a method to handle it:

@Name("messageHandler")
public class MessageHandler {

public String handleUnAssignedMessages() {
    // copy-paste from com.sun.faces.lifecycle.RenderResponsePhase
        Set clientIds = TypedCollections.dynamicallyCastSet(
                (Set) FacesContext.getCurrentInstance().getExternalContext().getRequestMap()
                .remove(RIConstants.CLIENT_ID_MESSAGES_NOT_DISPLAYED), String.class);
        if (clientIds != null)
            for (String clientId : clientIds) {
                Iterator messages = 
                    FacesContext.getCurrentInstance().getMessages(clientId);
                while (messages.hasNext()) {
                    FacesMessage message = messages.next();
                    if (message.getSeverity() == FacesMessage.SEVERITY_ERROR ||
                            message.getSeverity() == FacesMessage.SEVERITY_FATAL) {
                        String text = message.getSummary() + " " + message.getDetail();
                        FacesContext.getCurrentInstance().addMessage(null,
                                new FacesMessage(FacesMessage.SEVERITY_ERROR, text, text));
                    }
                }
            }
    return "";
}
}

This method simply collects all nondisplayed error messages and enqueues them without assigning them to a specific client id. Note that you'll have to include the jsf-impl.jar in your classpath for this code to compile. Having these messages enqueued, you can present them by a h:messages tag, or you can do anything programmatically with them.

Note that as you only have this information at the end of the render phase, you have to put the invocation of this method at the end of your page. If you want also to present them on the page with a h:messages tag, you have to put it after the invocation of the method above. You can then move the html output to the desired place in your document (above your forms, probably) with a simple javascript code:

#{messageHandler.handleUnAssignedMessages()} <h:messages layout="table" styleclass="msg_gl" infoclass="info-message" warnclass="warning-message" errorclass="error-message" globalonly="#{true}" showsummary="#{false}" showdetail="#{true}"> </h:messages>

So that's it.