dev@javaserverfaces.java.net

JSR-252 Issue 21 - Provided an additional "binding" attribute for the core Converter, Listener and Validator tags does not fit as spec doc says

From: Leonardo Uribe <lu4242_at_gmail.com>
Date: Mon, 15 Oct 2007 14:16:16 -0500

Hi

Reading the documentation of jsf-1_2-mrel-spec.pdf, says on section 9.4.5,
<f:converter>

"..... The implementation class for this action must meet the following
requirements:

     * Must extend javax.faces.webapp.ConverterJspTag.

     * The createConverter() method must:

       If binding is non-null, call binding.getValue() to obtain a reference
to the
       Converter instance. If there is no exception thrown, and
binding.getValue()
       returned a non-null object that implements
javax.faces.convert.Converter,
       register it by calling setConverter(). If there was an exception
thrown, rethrow
       the exception as a JspException.Use the converterId attribute if the
converter
       instance could not be created from the binding attribute. If the
converterId
       attribute is set, call the createConverter() method of the
Application
       instance for this application, passing converter id specified by
their converterId
       attribute. If the binding attribute was also set, store the converter
instance by calling
       binding.setValue(). Register the converter instance by calling
       setConverter(). If there was an exception thrown, rethrow the
exception as a
       JspException. "

Something similar for f:validator and f:actionListener

The problem is that the actual JSF RI does not do what in spec says to do,
but JSF RI has a correct behaviour

I have this example:

bindingCLV.jsp

<%@ page session="false" contentType="text/html;charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>

<html>
<f:view beforePhase="#{bindingCLVBean.beforePhase}"
    afterPhase="#{bindingCLVBean.afterPhase}">
<%_at_include file="inc/head.inc"%>
<body>
<f:loadBundle
basename="org.apache.myfaces.examples.resource.example_messages"
var="example_messages" />
<h1>Myfaces Examples JSF 1.2 Additions</h1>
<h:messages></h:messages>
<h:form id="form">
<h:panelGrid id="grid" columns="3">
<h:outputLabel value="BigInt" for="bigint"></h:outputLabel>
<h:inputText id="bigint" binding="#{bindingCLVBean.input1}" value="#{
bindingCLVBean.bigint}">
<f:converter converterId="javax.faces.BigInteger" binding="#{
bindingCLVBean.converterBigint}"/>
<f:validator validatorId="org.apache.myfaces.bindingCLV.DummyValidator"
binding="#{bindingCLVBean.validatorBigint}"/>
</h:inputText>
<h:message for="bigint"></h:message>
<h:outputLabel value="BigDecimal" for="bigdecimal"></h:outputLabel>
<h:inputText id="bigdecimal" binding="#{bindingCLVBean.input2}" value="#{
bindingCLVBean.bigdecimal}">
<f:converter converterId="javax.faces.BigDecimal" binding="#{
bindingCLVBean.converterBigdecimal}"/>
<f:validator validatorId="org.apache.myfaces.bindingCLV.DummyValidator"
                    binding="#{bindingCLVBean.validatorBigdecimal}"/>
</h:inputText>
<h:message for="bigdecimal"></h:message>
</h:panelGrid>
<h:commandButton id="button1" value="press me" action="#{
bindingCLVBean.update}" >
<f:actionListener type="org.apache.myfaces.bindingCLV.DummyActionListener"
binding="#{bindingCLVBean.listener}" />
</h:commandButton>
</h:form>
</body>
</f:view>
</html>

Bean:

package org.apache.myfaces.bindingCLV;

import java.math.BigDecimal;
import java.math.BigInteger;

import javax.faces.application.FacesMessage;
import javax.faces.component.html.HtmlInputText;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.event.ActionListener;
import javax.faces.event.PhaseEvent;
import javax.faces.validator.Validator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BindingCLVBean {

private BigInteger bigint;

private BigDecimal bigdecimal;

private Converter converterBigint;

private Converter converterBigdecimal;

private Validator validatorBigint;

private Validator validatorBigdecimal;

private HtmlInputText input1;

private HtmlInputText input2;

private ActionListener listener;

Log log = LogFactory.getLog(BindingCLVBean.class);

public void beforePhase(PhaseEvent phaseEvent){
FacesContext facesContext = FacesContext.getCurrentInstance();

facesContext.addMessage(null, new FacesMessage("This is the message for
phase before "+phaseEvent.getPhaseId().toString()));
facesContext.addMessage(null, new
FacesMessage("Component:"+this.getInput1()));
facesContext.addMessage(null, new
FacesMessage("Validator:"+this.getValidatorBigdecimal()));
facesContext.addMessage(null, new
FacesMessage("Converter:"+this.getConverterBigdecimal()));
facesContext.addMessage(null, new
FacesMessage("ActionListener:"+this.getListener()));
log.info("This is the message for phase before
"+phaseEvent.getPhaseId().toString()+" : ");
}

public void afterPhase(PhaseEvent phaseEvent){
FacesContext facesContext = FacesContext.getCurrentInstance();

facesContext.addMessage(null, new FacesMessage("This is the message for
phase after "+phaseEvent.getPhaseId().toString()));
facesContext.addMessage(null, new
FacesMessage("Component:"+this.getInput1()));
facesContext.addMessage(null, new
FacesMessage("Validator:"+this.getValidatorBigdecimal()));
facesContext.addMessage(null, new
FacesMessage("Converter:"+this.getConverterBigdecimal()));
facesContext.addMessage(null, new
FacesMessage("ActionListener:"+this.getListener()));
log.info("This is the message for phase after
"+phaseEvent.getPhaseId().toString()+" : ");
}

public BigInteger getBigint() {
return bigint;
}

public void setBigint(BigInteger bigint) {
this.bigint = bigint;
}

public BigDecimal getBigdecimal() {
return bigdecimal;
}

public void setBigdecimal(BigDecimal bigdecimal) {
this.bigdecimal = bigdecimal;
}

public Converter getConverterBigint() {
return converterBigint;
}

public void setConverterBigint(Converter converterBigint) {
this.converterBigint = converterBigint;
}

public Converter getConverterBigdecimal() {
return converterBigdecimal;
}

public void setConverterBigdecimal(Converter converterBigdecimal) {
this.converterBigdecimal = converterBigdecimal;
}

public Validator getValidatorBigint() {
return validatorBigint;
}

public void setValidatorBigint(Validator validatorBigint) {
this.validatorBigint = validatorBigint;
}

public Validator getValidatorBigdecimal() {
return validatorBigdecimal;
}

public void setValidatorBigdecimal(Validator validatorBigdecimal) {
this.validatorBigdecimal = validatorBigdecimal;
}

public String update(){
FacesContext context = FacesContext.getCurrentInstance();

return "update";
}

public HtmlInputText getInput1() {
return input1;
}

public void setInput1(HtmlInputText input1) {
this.input1 = input1;
}

public HtmlInputText getInput2() {
return input2;
}

public void setInput2(HtmlInputText input2) {
this.input2 = input2;
}

public ActionListener getListener() {
return listener;
}

public void setListener(ActionListener listener) {
this.listener = listener;
}

}

This example just work as a test to let me see what JSF RI are doing.

The first time a page is called it return the following:

    * This is the message for phase before RENDER_RESPONSE 6
    * Component:javax.faces.component.html.HtmlInputText_at_164de59
    * Validator:null
    * Converter:null
    * ActionListener:null

The spec says that createConverter() should resolve the binding!!!! But
looking on
tld file where is defined f:converter tag, JSF RI are using another file to
implement the tag
called com.sun.faces.taglib.jsf_core.ConverterTag, and if this class
implements what spec
says the previous thing should not happen. When you submit some data it
returns this:

    * This is the message for phase before APPLY_REQUEST_VALUES 2
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:null
    * Converter:null
    * ActionListener:null
    * This is the message for phase after APPLY_REQUEST_VALUES 2
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:null
    * Converter:null
    * ActionListener:null
    * This is the message for phase before PROCESS_VALIDATIONS 3
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:null
    * Converter:null
    * ActionListener:null
    * This is the message for phase after PROCESS_VALIDATIONS 3
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:org.apache.myfaces.bindingCLV.DummyValidator_at_10df4e2
    * Converter:javax.faces.convert.BigDecimalConverter_at_1485542
    * ActionListener:null
    * This is the message for phase before UPDATE_MODEL_VALUES 4
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:org.apache.myfaces.bindingCLV.DummyValidator_at_10df4e2
    * Converter:javax.faces.convert.BigDecimalConverter_at_1485542
    * ActionListener:null
    * This is the message for phase after UPDATE_MODEL_VALUES 4
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:org.apache.myfaces.bindingCLV.DummyValidator_at_10df4e2
    * Converter:javax.faces.convert.BigDecimalConverter_at_1485542
    * ActionListener:null
    * This is the message for phase before INVOKE_APPLICATION 5
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:org.apache.myfaces.bindingCLV.DummyValidator_at_10df4e2
    * Converter:javax.faces.convert.BigDecimalConverter_at_1485542
    * ActionListener:null
    * This is the message for phase after INVOKE_APPLICATION 5
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:org.apache.myfaces.bindingCLV.DummyValidator_at_10df4e2
    * Converter:javax.faces.convert.BigDecimalConverter_at_1485542
    * ActionListener:
org.apache.myfaces.bindingCLV.DummyActionListener_at_391da0
    * This is the message for phase before RENDER_RESPONSE 6
    * Component:javax.faces.component.html.HtmlInputText_at_14aa2db
    * Validator:org.apache.myfaces.bindingCLV.DummyValidator_at_10df4e2
    * Converter:javax.faces.convert.BigDecimalConverter_at_1485542
    * ActionListener:
org.apache.myfaces.bindingCLV.DummyActionListener_at_391da0

So, the binding for converter, validator and actionlistener are set only
when they are used, which is good
behaviour but bad comparing the jsf spec.

Looking further I have found doing again a black box test on JSF RI, when I
call getConverter() method,
it returns something like this:

converter: com.sun.faces.taglib.jsf_core.ConverterTag$
BindingConverter_at_1406eb6

Where is my javax.faces.convert.BigIntegerConverter<javax.faces.convert.BigIntegerConverter_at_168e429>instance?
 I suppose some delegate pattern inside this code that instantiate
the converter
validator or actionlistener the first time is used, and this delegate class
should be used for save and restore the state on the tree, but a different
impl as the jsf spec says.

on myfaces project this issue is MYFACES-1741.

I'm not an expert, and I don't know how request of changes for spec are
submitted, so I prefer send a mail to this discussion list and let you
decide what do you want to do.

And sorry for my bad english :)

regards

Att: Leonardo Uribe