[hibernate-dev] [BV] Path, string or object model
Emmanuel Bernard
emmanuel at hibernate.org
Thu Jun 18 16:56:08 EDT 2009
The constraintValidatorContext fluent API for building errors. Please
review.
Begin forwarded message:
> From: Emmanuel Bernard <emmanuel.bernard at jboss.com>
> Date: June 18, 2009 16:55:06 EDT
> To: jsr-303-eg at jcp.org
> Subject: Re: [jsr-303-eg] Path, string or object model
>
> Here is the solution for ConstraintValidatorContext.
> I've designed a fluent API to create error reports and add their
> message and potentially their sub nodes and the appropriate
> contextual information.
>
> Here is a usage example
>
> public boolean isValid(String value, ConstraintValidatorContext
> context) {
> //default path
> context.buildErrorWithMessage( "this detail is wrong" ).addError();
>
> //default path + "street"
> context.buildErrorWithMessage( "this detail is wrong" )
> .inSubNode( "street" )
> .addError();
>
> //default path + "addresses["home"].country.name
> context.buildErrorWithMessage( "this detail is wrong" )
> .inSubNode( "addresses" )
> .inSubNode( "country" )
> .inIterable().atKey( "home" )
> .inSubNode( "name" )
> .addError();
> return false;
> }
>
> I've used a nested interface model to keep things clean. Please
> review and comment.
>
> /**
> * Provide contextual data and operation when applying a given
> constraint validator
> *
> * @author Emmanuel Bernard
> */
> public interface ConstraintValidatorContext {
> /**
> * Disable the default error message and default
> ConstraintViolation object generation.
> * Useful to set a different error message or generate a
> ConstraintViolation based on
> * a different property
> */
> void disableDefaultError();
>
> /**
> * @return the current uninterpolated default message
> */
> String getDefaultErrorMessage();
>
> /**
> * Return an error builder building an error allowing to optionally
> associate
> * the error to a sub path.
> * The error message will be interpolated.
> * <p/>
> * To create the error, one must call either one of
> * the #addError method available in one of the
> * interfaces of the fluent API.
> * If another method is called after #addError() on
> * ErrorBuilder or any of its associated nested interfaces
> * an IllegalStateException is raised.
> * <p/>
> * If <code>isValid<code> returns <code>false</code>, a
> <code>ConstraintViolation</code> object will be built
> * per error including the default one unless {@link
> #disableDefaultError()} has been called.
> * <p/>
> * <code>ConstraintViolation</code> objects generated from such a
> call
> * contain the same contextual information (root bean, path and so
> on) unless
> * the path has been overriden.
> * <p/>
> * To create a different error, a new error builder has to be
> retrieved from
> * ConstraintValidatorContext
> *
> * Here are a few usage examples:
> * <pre>//create new error with the default path the constraint
> * //is located on
> * context.buildErrorWithMessage( "way too long" )
> * .addError();
> *
> * //create new error in the "street" subnode of the default
> * //path the constraint is located on
> * context.buildErrorWithMessage( "way too long" )
> * .inSubNode( "street" )
> * .addError();
> *
> * //create new error in the "addresses["home"].city.name
> * //subnode of the default path the constraint is located on
> * context.buildErrorWithMessage( "this detail is wrong" )
> * .inSubNode( "addresses" )
> * .inSubNode( "country" )
> * .inIterable().atKey( "home" )
> * .inSubNode( "name" )
> * .addError();
> * </pre>
> *
> * @param message new uninterpolated error message.
> */
> ErrorBuilder buildErrorWithMessage(String message);
>
> /**
> * Error builder allowing to optionally associate
> * the error to a sub path.
> *
> * To create the error, one must call either one of
> * the #addError method available in one of the
> * interfaces of the fluent API.
> * If another method is called after #addError() on
> * ErrorBuilder or any of its associated objects
> * an IllegalStateException is raised.
> *
> */
> interface ErrorBuilder {
> /**
> * Add a subNode to the path the error will be associated to
> *
> * @param name property
> * @return a builder representing the first level node
> */
> NodeBuilder inSubNode(String name);
>
> /**
> * Add the new error report to be generated if the
> * constraint validator mark the value as invalid.
> * Methods of this ErrorBuilder instance and its nested
> * objects returns IllegalStateException from now on.
> *
> * @return ConstraintValidatorContext instance the ErrorBuilder
> comes from
> */
> ConstraintValidatorContext addError();
>
> /**
> * Represent asubnode whose context is known
> * (ie index, key and isInIterable)
> */
> interface NodeBuilder {
> /**
> * Add a subNode to the path the error will be associated to
> *
> * @param name property
> * @return a builder representing this node
> */
> InIterableNodeBuilder inSubNode(String name);
>
> /**
> * Add the new error report to be generated if the
> * constraint validator mark the value as invalid.
> * Methods of the ErrorBuilder instance this object comes
> * from and the error builder nested
> * objects returns IllegalStateException from now on.
> *
> * @return ConstraintValidatorContext instance the ErrorBuilder
> comes from
> */
> ConstraintValidatorContext addError();
> }
>
> /**
> * Represent a subnode whose context is
> * configurable (ie index, key and isInIterable)
> */
> interface InIterableNodeBuilder {
> /**
> * Mark the node as being in an Iterable or a Map
> * @return a builder representing iterable details
> */
> InIterablePropertiesBuilder inIterable();
>
> /**
> * Add a subNode to the path the error will be associated to
> *
> * @param name property
> * @return a builder representing this node
> */
> InIterableNodeBuilder inSubNode(String name);
>
> /**
> * Add the new error report to be generated if the
> * constraint validator mark the value as invalid.
> * Methods of the ErrorBuilder instance this object comes
> * from and the error builder nested
> * objects returns IllegalStateException from now on.
> *
> * @return ConstraintValidatorContext instance the ErrorBuilder
> comes from
> */
> ConstraintValidatorContext addError();
> }
>
> /**
> * Represent choices for a node which is
> * in an Iterator or Map.
> * If the iterator is an indexed collection or a map,
> * the index or the key should be set.
> */
> interface InIterablePropertiesBuilder {
> /**
> * Define the key the object is into the Map
> *
> * @param key map key
> * @return a builder representing the current node
> */
> NodeBuilder atKey(Object key);
>
> /**
> * Define the index the object is into the List or array
> *
> * @param index index
> * @return a builder representing the current node
> */
> NodeBuilder atIndex(Integer index);
>
> /**
> * Add a subNode to the path the error will be associated to
> *
> * @param name property
> * @return a builder representing this node
> */
> InIterableNodeBuilder inSubNode(String name);
>
> /**
> * Add the new error report to be generated if the
> * constraint validator mark the value as invalid.
> * Methods of the ErrorBuilder instance this object comes
> * from and the error builder nested
> * objects returns IllegalStateException from now on.
> *
> * @return ConstraintValidatorContext instance the ErrorBuilder
> comes from
> */
> ConstraintValidatorContext addError();
> }
> }
> }
>
>
> On Jun 17, 2009, at 14:41, Emmanuel Bernard wrote:
>
>> Here is the solution I cooked. I think it's acceptable but add some
>> complexity in the ConstraintValidatorContext API (see other email).
>> Please review (pay special attention to the examples).
>>
>> A Path represents the path and is the one accepted by the path
>> consuming APIs. A Path is an Iterable of Nodes.
>>
>> /**
>> * Represent a navigation path from an object to another.
>> * Each path element is represented by a Node.
>> *
>> * The path corresponds to the succession of nodes
>> * in the order they are retured by the Iterator
>> *
>> * @author Emmanuel Bernard
>> */
>> public interface Path extends Iterable<Node> {
>> }
>>
>>
>> A node represent a path element.
>>
>> /**
>> * Represents an element of a navigation path
>> *
>> * @author Emmanuel Bernard
>> */
>> public interface Node {
>> /**
>> * Property name the node represents
>> * or null if the leaf node and representing an entity
>> * (in particular the node representing the root object
>> * has its name null)
>> */
>> String getName();
>>
>> /**
>> * True if the node represents an object contained in an Iterable
>> * or in a Map.
>> */
>> boolean isInIterable();
>>
>> /**
>> * The index the node is placed in if contained
>> * in an array or List. Null otherwise.
>> */
>> Integer getIndex();
>>
>> /**
>> * The key the node is placed in if contained
>> * in a Map. Null otherwise.
>> */
>> Object getKey();
>> }
>>
>> A few interesting points:
>> - the index / key is hosted by the node after the collection node
>>
>> Here are a few examples and their Node equivalent
>>
>> ""
>> 0: Node(name:null, isInIterable:false, index:null, key:null)
>>
>> "email"
>> 0: Node(name:email, isInIterable:false, index:null, key:null)
>>
>> "addresses"
>> 0: Node(name:addresses, isInIterable:false, index:null, key:null)
>>
>> "addresses["home"]" represent the bean level of an Address object
>> 0: Node(name:addresses, isInIterable:false, index:null, key:null)
>> 1: Node(name:null, isInIterable:true, index:null, key:home)
>>
>> "addresses["home"].city"
>> 0: Node(name:addresses, isInIterable:false, index:null, key:null)
>> 1: Node(name:city, isInIterable:true, index:null, key:home)
>>
>> "billingAddresses[3].country.name"
>> 0: Node(name:billingAddresses, isInIterable:false, index:null,
>> key:null)
>> 1: Node(name:country, isInIterable:true, index:3, key:null)
>> 2: Node(name:name, isInIterable:false, index:null, key:null)
>>
>>
>> ConstraintViolation renders a Path
>>
>> public interface ConstraintViolation<T> {
>> Path getPropertyPath();
>> }
>>
>> TraversableResolver accepts a path
>>
>> public interface TraversableResolver {
>> boolean isReachable(Object traversableObject,
>> String traversableProperty,
>> Class<?> rootBeanType,
>> Path pathToTraversableObject,
>> ElementType elementType);
>> ...
>> }
>>
>> PS: should String traversableProperty be Node traversableProperty ?
>>
>>
>> On May 27, 2009, at 12:19, Emmanuel Bernard wrote:
>>
>>> In several areas we do describe path:
>>> - ConstraintViolation
>>> - ConstraintValidatorContext (with addError(String, String) which
>>> allows to concatenate substrings
>>>
>>> So far we use the notion of string to represent it
>>> - address.name
>>> - cards[3].color
>>> - addresses["home"].city
>>>
>>> I have added the idea of using [] for simple Iterable objects (ie
>>> non indexed, like a Set)
>>> - accounts[].balance
>>>
>>> Anybody objects to that?
>>>
>>> Second point
>>> Do we want to replace this String approach with a path object mode?
>>>
>>> http://opensource.atlassian.com/projects/hibernate/browse/BVAL-143
>>> ______
>>> path are today strings with dot separating properties. But it
>>> break when Set or Iterable are used.
>>> We could replace that with
>>> --- First strawman, must evolve --
>>> class PathElement {
>>> String getName();
>>> PathElement getParentPath();
>>> boolean isIterable();
>>> boolean isIndexed();
>>> Object getIndex();
>>> //TODO int getIndex()?
>>>
>>> // not happy about that as it is only useful for
>>> Constraintciolation
>>> PathElement getChild();
>>> }
>>>
>>> PathElement would be used for Constraintvuilation, maybe CVContext
>>> etc
>>>
>>> can this be refactored using inheritance + generics to have an
>>> IndexedPathElement only when it matters (probably no unfortunately)
>>> ______
>>>
>>>
>>> Pros:
>>> - less string manipulation by the user and the
>>> TraversableResolver implementation
>>> - the map index no longer rely on "[" + toString() + "]" and is
>>> likely more easily handled
>>>
>>> Cons:
>>> - ConstraintValidatorContext becomes more complex as it needs to
>>> expose some kind of path element builder.
>>> - we would like need to standardize some kind of String
>>> serialization anyway
>>> - I don't see Pros as huge advantages
>>>
>>> WDYT?
>>
>> _______________________________________________
>> jsr-303-eg mailing list jsr-303-eg at jcp.org
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/hibernate-dev/attachments/20090618/a293ff13/attachment.html
More information about the hibernate-dev
mailing list