开发者

JSF 2 validators can't use "var" from dataTable

We've recently upgraded our systems from JSF 1.2 to JSF 2.0 and are in the process of making everything work. However, we're experiencing problems with validators, when used inside data tables or similar components. Basically, the problem is that the validator can't use the variable set by the data table.

Here's an example:

VALIDATOR:

package test;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validato开发者_开发知识库r.Validator;
import javax.faces.validator.ValidatorException;

@FacesValidator("test.TestValidator")
public class TestValidator implements Validator {

    private Integer length;

    public TestValidator() {
        super();
    }

    @Override
    public void validate(FacesContext context,
            UIComponent component, Object value) throws ValidatorException {

        String text = (String) value;
        if (text == null || text.trim().length() == 0) {
            return;
        }

        if (length != null && text != null && length.intValue() < text.length()) {
            String message = "The text is too long. It was " + text.length() +
                    ", but only " + length + " characters are allowed.";
            FacesMessage fm = new FacesMessage(
                    FacesMessage.SEVERITY_ERROR, message, null);
            throw new ValidatorException(fm);
        }
    }

    public Integer getLength() {
        return length;
    }

    public void setLength(Integer length) {
        this.length = length;
    }
}

TAGLIB:

<?xml version="1.0" encoding="UTF-8"?>
    <facelet-taglib
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
         version="2.0">
        <namespace>http://industry-supply.dk/test</namespace>
        <tag>
            <tag-name>testValidator</tag-name>
            <validator>
                <validator-id>test.TestValidator</validator-id>
            </validator>
            <attribute>
                <name>length</name>
                <required>true</required>
                <type>java.lang.Integer</type>
            </attribute>
        </tag>
    </facelet-taglib>

MANAGED BEAN:

package test;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean(name = "testBean")
@RequestScoped
public class TestBean {

    public TestBean() {
    }

    public String[] getKey() {
        return new String[]{
                    "0",
                    "1",
                    "2",
                    "3"
                };
    }

    public String[] getValue() {
        return new String[]{
                    "This is a text and it's too long.",
                    "This is a text and it's too long.",
                    "This is a text and it's too long.",
                    "This is a text and it's too long."
                };
    }
}

JSF:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:test="http://industry-supply.dk/test">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:form>
            <h:dataTable value="#{testBean.key}" var="k">
                <h:column>
                    <h:message for="val"/>
                    <h:inputText id="val" value="#{testBean.value[k]}">
                        <test:testValidator length="#{testBean.key[k]}"/>
                    </h:inputText>
                </h:column>
            </h:dataTable>
            <h:commandButton value="Submit"/>
        </h:form>
    </h:body>
</html>

When running the project, 4 input fields and a command button are shown. Each input field contains a 33 character long text. When you click "submit", the error message "The text is too long. It was 33, but only 0 characters are allowed." is shown for each row/field. This is wrong, because "test:testValidator length="#{testBean.key[k]}"" specifies length to 0 for the first row, 1 for the second row, 2 for the third row and 3 for the fourth row. So for the last row, the error message should have said: "The text is too long. It was 33, but only 3 characters are allowed.".

The problem is that the validator doesn't seem to have access to the k-variable from the dataTable component in the JSF file. This worked in JSF 1.2, but we can't get it to work in JSF 2.0. We've spend days on the problem now and really need some help. Ideas anyone?


Alright, it seems everything is working now. I've created a custom ValidatorHandler, as suggested by McDowell. Here are my changes:

TAGLIB:

<handler-class>test.CustomValidatorHandler</handler-class>

VALIDATOR:

package test;

import javax.el.ValueExpression;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

@FacesValidator("test.TestValidator")
public class TestValidator implements Validator {

    public TestValidator() {
        super();
    }

    @Override
    public void validate(FacesContext context,
            UIComponent component, Object value) throws ValidatorException {

        String text = (String) value;
        if (text == null || text.trim().length() == 0) {
            return;
        }

        Integer length = getLengthValue();

        if (length != null && text != null && length.intValue() < text.length()) {
            String message = "The text is too long. It was " + text.length() +
                    ", but only " + length + " characters are allowed.";
            FacesMessage fm = new FacesMessage(
                    FacesMessage.SEVERITY_ERROR, message, null);
            throw new ValidatorException(fm);
        }
    }

    private ValueExpression lengthExpression;

    public ValueExpression getLength() {
        return lengthExpression;
    }

    public void setLength(ValueExpression length) {
        this.lengthExpression = length;
    }

    public Integer getLengthValue() {
        Integer length = null;
        if (lengthExpression != null) {
            length = (Integer) lengthExpression.getValue(
                    FacesContext.getCurrentInstance().getELContext());
        }
        return length;
    }
}

VALIDATOR HANDLER:

package test;

import javax.el.ValueExpression;
import javax.faces.context.FacesContext;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.ValidatorConfig;
import javax.faces.view.facelets.ValidatorHandler;

public class CustomValidatorHandler extends ValidatorHandler {

    private ValueExpression lengthExpression;

    public CustomValidatorHandler(ValidatorConfig config) {
        super(config);
        FacesContext facesContext = FacesContext.getCurrentInstance();
        FaceletContext faceletContext = (FaceletContext) facesContext.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
        lengthExpression = config.getTag().getAttributes().get("length").getValueExpression(faceletContext, Integer.class);
    }

    @Override
    public void setAttributes(FaceletContext ctx, Object instance) {
//        super.setAttributes(ctx, instance);
        if (instance instanceof TestValidator) {
            TestValidator validator = (TestValidator) instance;
            validator.setLength(lengthExpression);
        }
    }
}


I believe your validator has a bug in it and you were just getting lucky under previous implementations.

From the documentation:

...if the Validator class wishes to have configuration property values saved and restored with the view, the implementation must also implement StateHolder.

When the view is restored during the POST operation the length state may no longer be present.

I don't remember what happens with ValueExpressions on validators - unlike components, validators tend not to have binding maps to hold them in. You may have to create your own ValidatorHandler or something - I haven't looked at this part of the API in detail.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜