-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 23 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Conditional or event-based validation
PostPosted: Tue Apr 01, 2008 11:33 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
Hello,

We have a very specific validation problem here with EJB3 persistent
entities. Suppose the following class skeletons:

class Foo {
@OneToMany
List<FooLink> links;

@OneToMany
List<FooLink> reverseLinks;
}

class FooLink {
@ManyToOne
Foo fromFoo;

@ManyToOne
Foo toFoo;

boolean bidirectional;
}

This may be a weird model, but it allows us to have both directional and
bidirectional links between Foo entities.

We want validation to prevent us from having duplicate links. We cannot
express this with a unicity constraint.

For instance we want to allow both links at once:
- link from Foo1 to Foo2 if NOT bidirectional
- link from Foo2 to Foo1 if NOT bidirectional

But if any link (Foo1 to Foo2 or Foo2 to Foo1) is bidirectional, we want
to only allow one such link.

This is not a trivial unicity constraint, and its details are not
entirely relevant to my question which follows:

Since it is not a trivial constraint, we cannot express it as a unicity
constraint and have to resort to validation constraints. It is not a
validation constraint which belongs to the FooLink class, but rather to
the Foo class.

This validation in the Foo class (bean if you prefer) would iterate the
links and reverseLinks properties to check for validation errors.

This iteration could potentially cause massive database loading costs
and has absolutely no justification if the only thing we modify in the
Foo bean instance is unrelated to those two lists. Suppose we have other
properties like Foo.name, changing the "name" property should not cause
the "links" validation to run.

We would like to be able to specify that a certain validation constraint
should be checked on certain occastions or events. For instance, we
would like this validation constraint to be run only when we modify our
"links" or "reverseLinks" properties, or their contents.

If we cannot specify this event clearly, we would at least want an API
which would set some "dirty bits" for validation. For instance making
our validation constraint (annotation) "lazy", and we would only
"enable" that validation when we know we have modified the "links"
properties.

Is this an area which could be covered by this JSR?
Considering the costs associated with high-level validation for
persistent entities, I hope we can specify the conditions that trigger
validation checks on a fine-grained basis.

Thanks.

_________________
--
Stéphane Épardaud


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 02, 2008 6:21 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
The JPA group is discussing an API to check the lazyness of a property.
It was partially intended to help Bean Validation to not load the whole database :)
So part of your usecase will be covered.

WRT deciding which constraint needs to be checked, the notion of groups helps to define subsets of constraints which can then be validated independently.

Note that either your application need to call the validation API or your application framework needs let you decide which group(s) you want to valid on a given event.

Typically the persistence API will probably only validate the default group.


Fedbacks are welcome though

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Sat Apr 05, 2008 5:56 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
I like the notion of groups, but they are only useful for programmer-triggered validation, no?
If one wants automatic validation of certain properties it is harder. I can imagine the following would be useful for us:

@Entity
class Foo {

@ConditionalValidationGroup(name = "mygroup", condition = ValidationConditionType.ON_MODIFY)
@ManyToMany
private List<FooLink> fooLinks;

@ConditionalValidation(group = "mygroup")
@AssertTrue
private boolean hasFunkyUnicityConstraint(){
...
}
}

The @ConditionalValidationGroup (bad name) would be used to mark a set of properties, and associate conditions (on modification, on insert, on persist, always...) and then these groups could be used to activate other validation methods with @ConditionalValidation.

In my example the hasFunkyUnicityConstraint() method would only be called when the fooLinks collection has been modified.

I can see another use case:

@Entity
class Foo {

@ConditionalValidationGroup(name = "checkmail", conditionValue = null),
@ConditionalValidation(group = "checkaddress")
@ValidateAddress
@NotNull
private Address address;

@ConditionalValidationGroup(name = "checkaddress", conditionValue = null)
@ConditionalValidation(group = "checkmail")
@ValidateMail
@NotNull
private String mailAddress;
}

In this example, the Foo entity would be required to have at least one of mailAddress or address valid and not null.

We actually have use-cases for this.

I haven't thought much on the syntax though, but I guess conditions could be activated based on events (on insert, update, on change, always) or property values (null, true...) and perhaps method calls?

_________________
--
Stéphane Épardaud


Top
 Profile  
 
 Post subject:
PostPosted: Sat Apr 05, 2008 9:12 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
I guess in my example @ConditionalValidation would require any @ConditionalValidationGroup conditions of that group to have a positive condition in order to enable every other validation annotation on that property.

Actually let me use better names:

This would defined some event types, pretty obvious what they mean, even though some of them are only meaningful for JPA and should be extensions. Not sure why we would want to validate based on deletion.

enum ValidationEventType {
ON_MODIFY, ON_INSERT, ON_MERGE, ON_DELETE, ALWAYS;
}

This would declare a validation trigger triggered if any of the "validators" validation annotations succeed on the annotated property, or if any of the "failedValidators" validation annotations fail on the annotated property. The "name" is always the name of the trigger.

@Target({METHOD, FIELD, TYPE})
@interface ValidationTriggerOnValue {
String name();
Annotation[] validatiors();
Annotation[] failedValidatiors();
}

This would declare a validation trigger triggered if the given UEL evaluates to true;

@Target({METHOD, FIELD, TYPE})
@interface ValidationTriggerOnUEL {
String name();
String uel();
}

This would declare a validation trigger triggered if any of the given events occur:

@Target({METHOD, FIELD, TYPE})
@interface ValidationTriggerOnEvent {
String name();
ValidationEventType[] event();
}

This would declare a validation trigger triggered if the annotated property is true:

@Target({METHOD, FIELD})
@interface ValidationTriggerOnTrue {
String name();
}

This allows to group validation triggers:

@Target({METHOD, FIELD, TYPE})
@interface ValidationTriggers {
Annotation[] value();
}

In addition to that every validation annotation should have an optional:

String[] validationTriggers() default {};

which would enable the validation only if ALL the given triggers (by name) are triggered.
I say "ALL" because by giving several triggers the same name we can "OR" them. That is a trigger named "foo" is triggered if ANY of the triggers named "foo" is triggered. So given that, it seems logical to give a way to "AND" trigger conditions too.


Here is a full-blown example:

Our weird many-to-many table:

@Entity
class FooLink {
@ManyToOne
Foo fromFoo;

@ManyToOne
Foo toFoo;

int linkType;
}

@Entity
class Foo {

// if we have a status of 1 we trigger the "exception" trigger
@ValidationTriggerOnUEL(name = "completed", uel = "status == 0")
@ValidationTriggerOnUEL(name = "exception", uel = "status == 1")
@MinMax(min = 0, max = 1)
int status;

// we only require this to be not empty if the "exception" trigger has been triggered
@NotEmpty(validationTriggers = {"exception"})
String exceptionReason;

// here we have our two directional link collections to be checked when modified
@ValidationTriggerOnEvent(name = "links", event = {ValidationEventType.ON_MODIFY})
@OneToMany(mappedBy = "toFoo")
List<FooLink> inverseLinks;

@ValidationTriggerOnEvent(name = "links", event = {ValidationEventType.ON_MODIFY})
@OneToMany(mappedBy = "fromFoo")
List<FooLink> links;

// this method would check our custom unicity constraint on the two lists of links
@AssertTrue(validationTriggers = {"links"})
private boolean checkLinkUnicity(){
...
}

// we require at least an address or an emailAddress, so if @NotNull fails, we trigger "noaddress"
@ValidationTriggerOnValue(name = "noaddress", failedValidators = {@NotNull})
@NotNull(validationTriggers = {"needsAddress"})
@Address
private Address address;

// if we have no email, we trigger "needsAddress"
@ValidationTriggerOnValue(name = "needsAddress", failedValidators = {@NotNull})
@NotNull(validationTriggers = {"noaddress"})
@EMailAddress
private String emailAddress;

// here we have some complex logic which will also help us determine whether we need an address
@ValidationTriggerOnTrue(name = "needsAddress")
private boolean doWeNeedAnAddress(){
// complex check here
}
}

Does any of this make sense?
Btw I would _love_ to be able to validate method parameters too.

_________________
--
Stéphane Épardaud


Top
 Profile  
 
 Post subject:
PostPosted: Mon Apr 07, 2008 4:57 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
I have to admit your proposal(s) is still confused in my mind.

Certainly your annotations are hard to read and their behavior do not come naturally. Also half of your proposal cannot be implement as is: try and compile it and you will be amazed on how much you can *not* do with annotations :)

That being said, from what I understand, you would like to define groups triggered by events. This is omething that crossed my mind but is essentially dependent on the client framework (JPA, WebBeans, JSF etc).
It is perfectly reasonable for such client frameworks to define some binding between an event and a validation group.

In persistence.xml you could have something like <validation event="update" group="change"/>

And then mark the constraints with groups change when they need to be applied at update time.

The oter side of your proposal about triggers on value or on other groups is more confused to me. What are you trying to achieve?

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 08, 2008 5:14 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
Sorry about the confusion. I think the names are still not good. and you're right these annotations don't compile (unfortunately).
What I'm trying to achieve is to have enable individual validators based on certain conditions. These conditions can be:
- UEL evaluation
- Value comparison (which is best done using validation annotations)
- Java evaluation (boolean result of a java method)
- Events (at least value change, at the property level)

The rationale is that I want to be able to do the following:

- Allow one property to be null iff another property is not null
- Enable certain property validators based on other property values
- Apply costly validation on a property if and only if that property changed (my link lists, but also postcodes for addresses)

To some extend the first two points can be done by writing a custom validator which would handle all the complexity, but really I expect some other people (like me) to prefer to have validation within the bean itself.

Here are the revised and renamed proposed annotations:

/**
* Placing this annotation on a property or type would declare a validation condition
* which would be true if the annotated property or type changed value.
*/
@Target({METHOD, FIELD, TYPE})
@interface ValidationConditionOnChange {
String name();
}

/**
* Placing this annotation on a property or type would declare a validation condition
* which would be true if the specified UEL evaluates to true.
*/
@Target({METHOD, FIELD, TYPE})
@interface ValidationConditionOnUEL {
String name();
String uel();
}

/**
* Placing this annotation on a property would declare a validation condition
* which would be true if the specified property evaluates to true.
*/
@Target({METHOD, FIELD})
@interface ValidationConditionOnTrue {
String name();
}

/**
* Placing this annotation on a property would declare a validation condition
* which would be true if all the specified validators succeed in validating, and all
* the failedValidators fail in validating. Those validators are referenced by name
* and can be located anywhere within the bean (possibly on other properties) by
* placing a validation annotation with the attribute "valueConditionName" to specify
* its name.
*/
@Target({METHOD, FIELD, TYPE})
@interface ValidationConditionOnValue {
String name();
String[] validatiors();
String[] failedValidatiors();
}

The appropriate array-containing annotations which allows us to put several of those conditions on a given element will be skipped for clarity.

For each validation annotation we would have to allow the following properties:

/** this validator should only attempt to validate if all the given conditions are true */
String[] validationConditions() default {};
/** this validator instance's name to be used by ValidationConditionOnValue */
String valueConditionName() default "";

Here is how you can allow one property to be null if the other one isn't:

@Entity class Foo {

// this declares a condition "noaddress" which is true if the "notnulladdress" validator
// fails to validate
@ValidationConditionOnValue(name = "noaddress", failedValidators = {"notnulladdress"})
// this declares a validator named "notnulladdress", which is only mandatory to pass
// validation if the "noemail" condition is true
@NotNull(validationConditions = {"noemail"}, valueConditionName = "notnulladdress")
Address address;

// this declares a condition "noemail" which is true if the "notnullemail" validator
// fails to validate
@ValidationConditionOnValue(name = "noemail", failedValidators = {"notnullemail"})
// this declares a validator named "notnullemail", which is only mandatory to pass
// validation if the "noaddress" condition is true
@NotNull(validationConditions = {"noaddress"}, valueConditionName = "notnullemail")
String email;
}

Using validators by name "valueConditionName" is handy because it allows us to reuse value checks in conditions. The validators can always be executed, but their validation failure are only meaningful if their validationConditions are all true.

Here is how we would enable one validator based on the value of another value:

@Entity class Foo {

// define a condition which is true iff status is greater or equal to 300
@ValidationConditionOnUEL(name = "exception", uel = "status >= 300")
int status;

// require the message property to not be empty iff the "exception" condition is true
@NotEmpty(validationConditions = {"exception"})
String message;
}

And here is how we would limit costly validation on some property changes:
The following would only call the costly validation of @Address if postCode, city or country change value. Nobody wants to recheck an address if these fields did not change. This is just as valid in JSF which does form validation and can recognise that a field was edited, or in JPA which has the previous DB version.

@Address(validationConditions = "changedaddress")
@Entity class Address {

@ValidationConditionOnChange(name = "changedaddress")
String postCode;

@ValidationConditionOnChange(name = "changedaddress")
String city;

@ValidationConditionOnChange(name = "changedaddress")
String country;

String streetName;
String postBox;
}

Other example:

@Entity class Foo {

@ValidationConditionOnChange(name = "links")
@ManyToOne
FooLink reverseLinks;

@ValidationConditionOnChange(name = "links")
@ManyToOne
FooLink forwardLinks;

@AssertTrue(validationConditions = {"links"})
private boolean hasNoDuplicateLink(){
// costly loops
}
}

Is this any clearer?

_________________
--
Stéphane Épardaud


Last edited by FroMage on Fri Apr 25, 2008 10:30 am, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 10, 2008 6:38 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
I have to admit I am really not convinced.
It implies some sort of half assed dependency graph completely relying on untype safe strings.
On top of that, the notion of value change is very relative. I am sure JSF has a different notion of value change than an ORM (because the contextual info is different).

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 11, 2008 5:54 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
I'm not particularly attached to this solution.

However I am attached to being able to have conditional validation, and that implies some mechanism to define conditions.

I don't think there's anything better than using untype-safe strings to refer to annotations, this is used by validation groups, or by @OneToMany(mappedBy ="") for instance. I'd take anything better.
I also don't think the dependency graph has to be half-assed, it could be correctly implemented ;)
Note also that the conditional validation is not only about change triggering, although being able to avoid running validation for absolutely no good reason is key to efficiency.
Conditions also include UEL, Java and simple value checks.

If there's any other way to achieve the same results, then I'm a taker.
I'm also taker on any improvement/change to my proposal.
If not, would an implementation of my proposal help in convincing you?

_________________
--
Stéphane Épardaud


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 23, 2008 7:54 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
It's really that I find these dependency declarations really really hard to read compared to a class level constraint.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 23, 2008 8:04 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
What do you guys thing of http://forum.hibernate.org/viewtopic.php?p=2383422
I am not sure it's feasible though.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 24, 2008 4:13 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
emmanuel wrote:
It's really that I find these dependency declarations really really hard to read compared to a class level constraint.


I quite understand your point of view.

However I do think it's a matter of style, and therefore entirely subjective.

I, for one, prefer validation inside my bean, in a single location for easier code maintenance.
The fact that validation could be split up between field annotations and entity-specific validation in another class makes it harder to maintain.

I do understand my proposal is somewhat verbose, I'm not saying it couldn't be improved, but from all the different proposals I see about conditional validation I gather that there is an important need from people to locate more validation logic inside the bean itself.

_________________
--
Stéphane Épardaud


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 24, 2008 4:14 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
emmanuel wrote:
What do you guys thing of http://forum.hibernate.org/viewtopic.php?p=2383422
I am not sure it's feasible though.


I think if I'm not mistaken that my proposal would allow what is proposed in that other thread. And then some more :)

_________________
--
Stéphane Épardaud


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 24, 2008 1:17 pm 
Beginner
Beginner

Joined: Fri Nov 03, 2006 3:21 pm
Posts: 30
To better understand I have two question:

1) Event-based validation would be used for automated validation of bean?
Currently in HV I do this:

Save:
Code:
MyBean myBean = new MyBean();
myBean.set...

InvalidValue[] validationMessages = validator..getInvalidValues(myBean);
...

dao.save(myBean);


Delete:
Code:
MyBean myBean = new MyBean();
myBean.set...

dao.delete(myBean);


In delete action I don't call the validator.
This proposal is to validate the bean automatically in save?
If yes, after the event 'save' how to get list of return messages -> InvalidValue[]?

2) How can we write validations for the two examples of this post (using these annotations):
http://forum.hibernate.org/viewtopic.php?t=985722


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 25, 2008 9:58 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
1/ HV already can do automatic validation when saving. The "event" part of my proposal refers to being able to skip validating properties which haven't changed.

2/ I'm not proposing to get rid of the validation groups, I think it's really nice.

Your first example would be like this:

@NotNull
@Lengths({
@Length(validationConditions="user", min=4, max=20),
@Length(validationConditions="admin", min=8, max=20)
})
public String getPassword() {
return password;
}

@ValidationConditionOnUELs({
@ValidationConditionOnUEL(name="admin", uel="this.userType == 'admin'"),
@ValidationConditionOnUEL(name="user", uel="this.userType == 'user")
})
public String getUserType(){
return userType;
}

Your second:

@ValidationConditionOnTrue(name = "sendMail")
public Boolean isReceiveNews() {
return receiveNews;
}

@ValidationConditionOnTrues({
@ValidationConditionOnTrue(name = "sendMail"),
@ValidationConditionOnTrue(name = "phone")
})
public Boolean isReceiveAlerts() {
return receiveAlerts;
}

@NotNull(validationConditions="sendMail")
public String getMail() {
return mail;
}

@NotNull(validationConditions="phone")
public String getTelephone() {
return telephone;
}

Basically Since several conditions can have the same name, using that condition in an annotation's validationConditions means that _any_ of the conditions of the same name have to be met. If you use several validationConditions then they _all_ have to be met. This allows you to do both "OR" and "AND" for conditions.

The only downside is having to declare all these array conditions, but that's a limitation of annotations and we should bring it up with the JSR under way.

_________________
--
Stéphane Épardaud


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 25, 2008 10:52 am 
Regular
Regular

Joined: Tue Apr 01, 2008 11:10 am
Posts: 69
Here is how we could activate group validation based on those conditions:

/**
* Declares a validation group which should be activated when all the given conditions
* are met.
*/
@Target({TYPE})
@interface ValidationGroupDef {
String name();
String[] validationConditions() default {};
}

This would allow things like:

@Entity
@ValidationGroupDef(name="approved", validationConditions = {"whenApproved"})
public class Budget{

@NotNull(groups = "default")
private BigDecimal budgetAmount;

@ValidationConditionOnTrue(name = "whenApproved")
private boolean approved = false;

@NotNull(groups = "approved")
private Date approvalDate;
}

Which can be handy if you're really using groups, but in such a small example could just be:

@Entity
public class Budget{

@NotNull
private BigDecimal budgetAmount;

@ValidationConditionOnTrue(name = "whenApproved")
private boolean approved = false;

@NotNull(validationConditions = {"whenApproved"})
private Date approvalDate;
}

Although it may seem like validation groups and groups formed by validation conditions of the same name are redundant, I think they are really different. For instance you can have a "simple" and an "advanced" group, one of which contain conditional validation:

@Entity
@GroupSequence(name="default", sequence={"basic", "complex"})
public class Budget{

@NotNull(groups = "basic")
private BigDecimal budgetAmount;

@ValidationConditionOnTrue(name = "whenApproved")
private boolean approved = false;

@NotNull(groups = "complex", validationConditions = {"whenApproved"})
private Date approvalDate;

@Valid(groups = "complex")
private BudgetDescription description;
}

Here the "complex" group would check two properties, only one of which is conditional.

_________________
--
Stéphane Épardaud


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 23 posts ]  Go to page 1, 2  Next

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.