eclipse-ee4j / mojarra

Mojarra, a Jakarta Faces implementation
Other
162 stars 109 forks source link

Attributes on custom FacesValidators not working when managed=true #4814

Closed brooksie closed 1 year ago

brooksie commented 3 years ago

I get the following error when using a custom validator with attributes under JSF2.3 / Mojarra 2.3.14.SP01 / Wildfly21 if the @FacesValidator tag includes "managed=true". When managed is not set, the validator works correctly.

SEVERE [javax.enterprise.resource.webcontainer.jsf.facelets.tag.meta] (default task-2) /index.xhtml @18,58 maxValue="99" Unhandled by MetaTagHandler for type com.sun.faces.cdi.CdiValidator

I know that in this example using "managed=true" isn't necessary, but I'd also like to inject other resources into other custom validators in future.

To reproduce, here is the source of the very simple validator:

package com.example;

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(value = "com.example.validateMaxInt", managed = true )
public class ValidateMaxInt implements Validator<Integer> {

    private static final Integer MAX_VALUE = 999999;
    private Integer maxValue;

    @Override
    public void validate(FacesContext fc, UIComponent uic, Integer t) throws ValidatorException {

        if (t == null) {
            return;
        }

        if (t > ((this.maxValue == null) ? MAX_VALUE : this.maxValue)) {
            throw new ValidatorException(new FacesMessage(
                    FacesMessage.SEVERITY_ERROR, "Value is outside permitted range", null));
        }
    }

    public Integer getMaxValue() {
        return maxValue;
    }

    public void setMaxValue(Integer maxValue) {
        this.maxValue = maxValue;
    }

}

Here is the facelet-taglib:

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib 
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_3.xsd"
    version="2.3">

    <namespace>http://example.com/mytags</namespace>

    <tag>
        <tag-name>validateMaxInt</tag-name>
        <validator>
            <validator-id>com.example.validateMaxInt</validator-id>
        </validator>
        <attribute>
            <name>maxValue</name>
            <type>java.lang.Integer</type>
        </attribute>
    </tag>

</facelet-taglib>

A simple JSF web page:

<?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://xmlns.jcp.org/jsf/html"
      xmlns:ecm="http://example.com/mytags">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h1>Hello from Facelets</h1>

        <h:form>

            <h:panelGrid columns="3" >

                <h:outputLabel for="@next" value="Integer (valid &lt;99):" />
                <h:inputText id="int1" value="#{indexBean.int1}" >
                    <ecm:validateMaxInt maxValue="99"  />
                </h:inputText>
                <h:message for="@previous" />

            </h:panelGrid>
            <h:commandButton action="#{indexBean.doIt}" value="Validate" />
        </h:form>
    </h:body>
</html>

The JSF bean:

package com.example;

import java.io.Serializable;
import javax.inject.Named;
import javax.faces.view.ViewScoped;

@Named
@ViewScoped
public class IndexBean implements Serializable {

    private Integer int1;

    public IndexBean() {
    }

    // + getters and setters

    public void doIt() {
    }

}

Configuration bean:

package com.example;

import javax.enterprise.context.ApplicationScoped;
import javax.faces.annotation.FacesConfig;

@FacesConfig(version = FacesConfig.Version.JSF_2_3)
@ApplicationScoped
public class ConfigurationBean {
}
BalusC commented 3 years ago

Indeed, this is not working as intended.

In the meanwhile consider using OmniFaces. Just install it and managed=true is not anymore necessary.

@mnriem I remember you've implemented this for Mojarra. Any insight into how to best fix this without replacing it altogether with similar approach as done in OmniFaces? (where it basically just makes them managed beans instead of wrapping in a CDI delegate)

manorrock commented 3 years ago

The managed attribute was introduced as a means of not breaking the original contract of Converters/Validators (e.g. without CDI in the mix they would only get constructed once, they were not eligible for injection, and they were bound to a specific class-type, etceteras). Now if you are OK going all in on CDI then you could consider replacing all this and making sure that folks would not be surprised when using it as CDI artifacts.

fcorneli commented 3 years ago

The example would not work anyway as you need to implement StateHolder when using attributes on a Validator, no?

BalusC commented 2 years ago

The managed attribute was introduced as a means of not breaking the original contract of Converters/Validators (e.g. without CDI in the mix they would only get constructed once, they were not eligible for injection, and they were bound to a specific class-type, etceteras). Now if you are OK going all in on CDI then you could consider replacing all this and making sure that folks would not be surprised when using it as CDI artifacts.

Absolutely. The managed=true impl can just be rewritten in such way to simply make them managed beans instead of wrapping in a CDI delegate. Folks will likely not be surprised because managed=true is already not the default setting in first place.

mnriem commented 1 year ago

For Mojarra 2.3 and earlier please contact your vendor for support (RedHat, IBM, Oracle, Omnifish, Payara, etceteras)