Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does f:validateDoubleRange only work for @SessionScoped?

Can someone explain to me why Foo in my example is always null when it gets to the validateDoubleRange class? The end result is the min value for the validator is always 0. The number 3 displays just fine on the page when in the outputText element. It validates fine if I make the bean @SessionScoped instead of @ViewScoped

Controller:

import java.io.Serializable;
import java.math.BigDecimal;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ViewScoped
@ManagedBean(name = "fooController")
public class FooController implements Serializable {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FooController.class);
    private static final long serialVersionUID = 1L;
    private Foo foo;
    private BigDecimal amount;
    private Long fooId;

    public Long getFooId() {
        return fooId;
    }

    public void setFooId(Long fooId) {
        this.fooId = fooId;
        this.foo = new Foo();
        foo.setFooId(fooId);
        foo.setMinAmount(Double.valueOf(3));
        foo.setMaxAmount(Double.valueOf(10));
    }

    public Foo getFoo() {
        return foo;
    }

    public void sendAmount() {
        log.debug("sendAmount: " + amount);
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }

    public static class Foo {

        private Long fooId;
        private Double minAmount;
        private Double maxAmount;

        public Foo() {
        }

        public void setFooId(Long fooId) {
            this.fooId = fooId;
        }

        public void setMinAmount(Double minAmount) {
            this.minAmount = minAmount;
        }

        public void setMaxAmount(Double maxAmount) {
            this.maxAmount = maxAmount;
        }

        public Long getFooId() {
            return fooId;
        }

        public Double getMaxAmount() {
            return maxAmount;
        }

        public Double getMinAmount() {
            return minAmount;
        }
    }
}

JSP:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
            xmlns:ui="http://java.sun.com/jsf/facelets"
            xmlns:f="http://java.sun.com/jsf/core"
            xmlns:h="http://java.sun.com/jsf/html"
            >
<f:metadata>
    <f:viewParam name="fooId" value="#{fooController.fooId}" />        
</f:metadata>
<h:form id="myForm">
    <h:outputText value="This is correctly displayed: '#{fooController.foo.minAmount}'"/><br/>
    <h:outputText value="My Input:" />
    <h:inputText id="myInput"
                 value="#{fooController.amount}" 
                 required="true"
                 >
        <f:convertNumber maxFractionDigits="2"/>
        <f:validateDoubleRange minimum="#{fooController.foo.minAmount}" maximum="80"/>
    </h:inputText>
    <h:message for="myInput"/>
    <br/>
    <h:commandButton id="myButton"
                     value="Save Amount"
                     action="#{fooController.sendAmount}"
                     >
    </h:commandButton>
</h:form>
</ui:composition>

I am using JSF 2 on JBoss 6.1

If you are out there @BalusC, this is the simplest example I could come up with, and is the same problem as yesterday, just, I hope, clarified and simpler to replicate.

-- edit, I should add that fooId is set via a view param, so you need ?fooId=3 at the end of your URL.

like image 314
Simon Avatar asked Sep 16 '11 13:09

Simon


1 Answers

I now see why I couldn't reproduce this yesterday. In my playground environment I had accidentally the following context param set differently from the default value:

<context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    <param-value>false</param-value>
</context-param>

This problem is related to JSF issue 1492. A <f:validateDoubleRange> whose attributes are by EL bound to a view scoped bean property will implicitly recreate the whole bean due to a nasty chicken-egg issue, because validators are created on a per-request basis and those properties are to be passed on validator's construction.


If you do not want to disable partial state saving (very reasonable), then your best bet is to create and hold the validator in the view scoped managed bean yourself:

public Validator getValidator() {
    return new DoubleRangeValidator(foo.getMaximum(), foo.getMinimum());
}

(note, doing this stuff in a getter is a bad design, but since you're preparing foo in a setter instead of a preRenderView listener method, there's no other way, otherwise it would be done right in the same listener method)

with

<h:inputText validator="#{fooController.validator.validate}" ...>

Or, alternatively, to create a custom validator for that which replaces the <f:validateDoubleRange>:

<f:validator validatorId="bindableDoubleRangeValidator" />
<f:attribute name="minimum" value="#{fooController.foo.minAmount}" />
<f:attribute name="maximum" value="#{fooController.foo.maxAmount}" />

with

package com.example;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.DoubleRangeValidator;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.ValidatorException;

@FacesValidator("bindableDoubleRangeValidator")
public class BindableDoubleRangeValidator extends DoubleRangeValidator {

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        setMinimum((Double) component.getAttributes().get("minimum"));
        setMaximum((Double) component.getAttributes().get("maximum"));
        super.validate(context, component, value);
    }

}

Update: the chicken-egg issue 1492 is fixed since Mojarra 2.1.18 (January 2013). So if you stumble upon this these days, then you could also consider to just upgrade.

like image 54
BalusC Avatar answered Oct 28 '22 10:10

BalusC