0

I created the following Entity and embeddable:

@Entity
public class Person implements Serializable {

    @NotNull
    @Pattern(regexp = "([A-Z][a-z]*)*", message = "First Name must match pattern ([A-Z][a-z]*)*")
    private String firstName;

    @Valid
    @ElementCollection
    private List<Email> email;
...
}

@Embeddable
public class Email implements Serializable {

    @NotNull (message = "Email Address may not be null")
    @Pattern(regexp = "[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}", message = "Email Address must match pattern [-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}")
    private String address;
...
}

I then tested the GlassFish4/Hibernate Validator 5.0.0 cascading validation functionality using a PersonCreater.xhtml JSF page and a PersonManager stateless session EJB as follows:

  1. Created Person.
  2. Added email with address of "adsj alkjdfa" to the Person email list.
  3. Added email with address of "#AV@#$R" to the Person email list.
  4. Persisted the object.

Unfortunately, I did not receive any errors and the Person object with the invalid email addresses was persisted to the database.

As a test, I changed the email property on my Person class from a list of email to a single email as shown below.

@Entity
public class Person implements Serializable {

    @Valid
    private Email email;
...
}

I then performed the following test.

  1. Created Person.
  2. Added email with address of "adsj alkjdfa" to the Person email list.
  3. Persisted the object.

As expected, I received a constraint violation stating that email did not match pattern and the Person was not persisted in the database.

As a final test, I added the following code to the addEmail method of my JSF CDI Bean in order to manually check the validations.

    Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    Set<ConstraintViolation<Email>> emailCV = validator.validate(newEmail);
    logger.debug("Email constraint violations: " + emailCV);

    Set<ConstraintViolation<Person>> personCV = validator.validate(person);
    logger.debug("Person constraint violations: " + personCV);

    Set<ConstraintViolation<Person>> personEmailCV = validator.validateProperty(person, "email");
    logger.debug("Person Email constraint violations: " + personEmailCV);

When I entered an invalid email address, I received the following messages:

  • Email constraint violations: [ConstraintViolationImpl{interpolatedMessage='Email Address must match pattern ...
  • Person constraint violations: [ConstraintViolationImpl{interpolatedMessage='Email Address must match pattern...
  • Person Email constraint violations: []

I expected the validator.validate(newEmail); method call would throw the Pattern violation because I was validating the newEmail by itself.

However, I did not expect the validator.validate(person); method call would find the Pattern violation, because I thought my problem was associated with my Entity and Embeddable classes.

These results tells me that I have configured my Entity and Embeddable classes correctly but that for some reason GlassFish is not automatically validating the email property when I create the Person using my JSF page or when I persist the Person using my EJB.

Do I need to configure GlassFish to automatically perform the cascading validation?

Finally, I don't know why the validator.validate(person); method call found the Pattern violation and the validator.validateProperty(person, "email"); method call did not.

The following is my persistence-unit from my persistence.xml.

<persistence-unit name="ELISPU" transaction-type="JTA">

    <jta-data-source>jdbc/elis</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>

    <properties>
        <property name="javax.persistence.schema-generation.database.action" value="create" />
    </properties>

</persistence-unit>

The following is the a more complete view of my Person Class:

@Entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@NamedQueries({
        @NamedQuery(name = "Person.findAll", query = "SELECT p FROM Person p ORDER BY p.lastName, p.firstName"),
        @NamedQuery(name = "Person.findByName", query = "SELECT p FROM Person p WHERE p.lastName = :lastName and p.firstName = :firstName and p.middleName = :middleName"),
        @NamedQuery(name = "Person.findByShortName", query = "SELECT p FROM Person p WHERE p.shortName = :shortName") })
public class Person implements Serializable {

    final static Logger logger = LoggerFactory.getLogger(Person.class.getName());

    static final long serialVersionUID = 1L;

    @TableGenerator(name = "Person_Generator", table = "ID_Gen", pkColumnName = "GEN_NAME", valueColumnName = "GEN_VAL", initialValue = 0, allocationSize = 1)
    @Id
    @GeneratedValue(generator = "Person_Generator")
    @XmlAttribute
    private Long id;
    @Version
    @XmlAttribute
    private Integer version;
    private String prefixName;
    @NotNull
    @Pattern(regexp = "([A-Z][a-z]*)*", message = "First Name must match pattern ([A-Z][a-z]*)*")
    private String firstName;
    @Pattern(regexp = "([A-Z][a-z]*)*", message = "Middle Name must match pattern ([A-Z][a-z]*)*")
    private String middleName;
    @NotNull 
    @Pattern(regexp = "([A-Z][a-z]*)*", message = "Last Name must match pattern ([A-Z][a-z]*)*")
    private String lastName;
    private String suffixName;
    @NotNull
    @Pattern(regexp = "([A-Z][a-z]*)*", message = "Familiar Name must match pattern ([A-Z][a-z]*)*")
    private String familiarName;
    @NotNull
    @Column(unique = true)
    @Pattern(regexp = "[a-z0-9]*", message = "Short Name must match pattern [a-z0-9]*")
    private String shortName;
    private String description;

    @Valid
    @ElementCollection
    private List<Email> email;
    @ElementCollection
    private List<Voice> voice;
    @ElementCollection
    private List<Address> addresses;

    private ELISFile picture;
    @XmlElementWrapper(name = "notes")
    @XmlElement(name = "note")
    @ElementCollection
    private List<EntityNote> notes;
    @XmlTransient
    // @XmlElementWrapper(name="history")
    // @XmlElement(name="event")
    @NotNull
    @ElementCollection
    private List<EntityEvent> history;

    public Person() {

        logger.debug("Person created.");

        email = new ArrayList<Email>();
        voice = new ArrayList<Voice>();
        addresses = new ArrayList<Address>();
        notes = new ArrayList<EntityNote>();
        history = new ArrayList<EntityEvent>();

    }
...

The following is a more complete version of my Email class:

@Embeddable
@XmlAccessorType(XmlAccessType.FIELD)
public class Email implements Serializable {

    final static Logger logger = LoggerFactory.getLogger(Email.class.getName());

    static final long serialVersionUID = 1L;

    @NotNull (message = "Email Type may not be null")
    @Enumerated(EnumType.STRING)
    private EmailType type;
    @NotNull (message = "Email Address may not be null")
    @Pattern(regexp = "[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}", message = "Email Address must match pattern [-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}")
    private String address;
    private String description;
    ...
Reed Elliott
  • 223
  • 2
  • 15

2 Answers2

1

The problem is not Glassfish, but your model and how JSF integrates with Bean Validation. As you might have already discovered JSF is not using Validator#validte, but rather Validator#validatePropery to validate constraints on single properties. The former validates full object graphs whereas the latter only validates constraints specified on a given property. Cascaded validation is not executed by Validator#validateProperty.

The validation on JPA level (in case you enable it) should work though. There a full entity validation occurs on the different JPA life cycle callbacks.

Hardy
  • 18,659
  • 3
  • 49
  • 65
  • Interesting. JSF uses validateProperty to partially validate the Person entity and validateProperty does not honor the @Valid annotation on the Person's email property. This explains why my JSF Client does not report the constraint violation. However, I still do not understand why the JPA level is not validating the email property when I persist the Entity. Everything that I have read states that bean validation is enabled by default. Do I have to add some property to my persistence.xml file to enable JPA level bean validation? Thank you for your help! – Reed Elliott Sep 01 '14 at 22:09
  • Validation on JPA lifecycle events are enabled by default, but can also be controlled by some properties. See http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#d0e5193. Why it is not working for you is hard to tell, especially since in your example you are not showing your JPA annotations/configuration. I recommend to turn on debug level logging to see whether validation occurs. If you want more feedback, you'll need to update your question with more information. – Hardy Sep 02 '14 at 09:31
  • I added an org.hibernate.validator Logger to my GlassFish 4 server-config and observed where the validator is validating that the firstName property is not null and matches the pattern. If I add a @Size(min =1) annotation to the email property I observe that the validator validates the size of the list on the email property but does not validate the address property of the email objects that comprise the list. I updated my original question with some configuration information. – Reed Elliott Sep 03 '14 at 04:10
0

The answer to this question has the following two parts where Part 1 explains why JSF did not catch the validation error, and Part 2 explains why JPA did not catch the validation error.

Part 1

As Hardy stated in his answer, JSF uses Hibernate Validator's validateProperty method to partially validate the Person entity and validateProperty does not honor the @Valid annotation on the Person's email property.

Part 2

JPA/EclipseLink in GlassFish 4.0 and 4.1 does not cascade into the elements of the list property when the property is annotated with @Valid @ElementCollection. If I remove the @ElementCollection from my property, I can see the cascading validation working as expected but this breaks JPA so is not a good workaround. I logged this issue in the GlassFish bug tracking system as GLASSFISH-21184.

I worked around the bug by manually performing the validation before calling persist on my new person as shown below:

Set<ConstraintViolation<Person>> personCV = validator.validate(person);

if (personCV.size() > 0)
    throw new ConstraintViolationException(personCV);

em.persist(person);
Reed Elliott
  • 223
  • 2
  • 15