-
Notifications
You must be signed in to change notification settings - Fork 573
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Explore FreeForm validation v2 #989
base: main
Are you sure you want to change the base?
Conversation
…pl#buildNewLocalExecutionContext()
…scadingMetaData As in case of property holders we would need to produce metadata not based on a class of a current object at runtime but based on a string representing the constraint mapping, we need to delegate this to cascading metadata as that's the place where this information will be stored.
accessor creator with simple a implementation for Map
and rename it As metadata manager will serve as provider of bean metadata as well as property holder metadata, it'll be better to keep it in specific package
class and create corresponding analog for property holders
…etadata for property holders
…d add a simple test
@@ -739,23 +739,14 @@ private void validateCascadedContainerElementsInContext(Object value, BaseBeanVa | |||
|
|||
private ValueContext<?, Object> buildNewLocalExecutionContext(ValueContext<?, ?> valueContext, Object value) { | |||
ValueContext<?, Object> newValueContext; | |||
if ( value != null ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
based on the usage of this method null values could not be passed in here, as they go through a different logical branch. Reducing the number of different variations of ValueContext
creations helps to fit in the property holders in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I merged this one.
/** | ||
* @author Marko Bekhta | ||
*/ | ||
public class PropertyHolderProperty implements Property { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property holder property can be used for any kind property holder. It should work just fine with Maps JsonObjects and anything else. All the accessing logic is provided by a property accessor defined by PropertyAccessorCreator
interface. PropertyAccessorCreatorProvider
gives a creator based on a property holder type. This allows to store the user entered metadata once and then create "real" validation metadata based on a property holder type.
@@ -47,6 +50,8 @@ | |||
private static final CascadingMetaDataBuilder NON_CASCADING = | |||
new CascadingMetaDataBuilder( null, null, null, null, false, Collections.emptyMap(), Collections.emptyMap() ); | |||
|
|||
private final String mapping; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now property holders and regular beans are using the same cascading metadata builder. When I've tried to break it into smaller reusable pieces, I've broke it so badly 😄 that I decided to revert the changes for now and just use the same builder. But this still is needed to be done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let's try to have a todo list somewhere so that we don't forget about it.
public class MetaConstraintBuilder<A extends Annotation> { | ||
|
||
private final ConstraintDescriptorImpl<A> constraintDescriptor; | ||
private final ConstraintLocation.Builder locationBuilder; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The part about location builder in this commit can be ignored. It will be removed in later commits. And I think about removing this builder as well. I think that storing the ConstraintDefs will be enough and this logic will be moved elsewhere.
} | ||
else { | ||
validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, beanMetaData.getBeanClass(), beanMetaData.getDirectMetaConstraints(), Group.DEFAULT_GROUP ); | ||
List<Class<? super U>> classHierarchy = beanMetaData.getClassHierarchy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan to make BeanMetaData
an abstract class with regular and property holder impls. Then in case of property holder there will be nothing else but the property holder class in this list.
Also note the change itself. Before this we were accessing a map to get the metadata that we already have. With this we remove that one additional map access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain the rationale of this change?
It's hard to see the difference, considering all the method is rewritten in the diff.
Once you got this cleared, I think I'll merge it separately (and probably run a JMH benchmark to check everything is OK).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah ... Let me try to explain how I got here. The main "problem" that I was trying to "solve" was to reduce the number of places where constraintMetaDataManager.getBeanMetaData( clazz );
is called. In case of property holders we cannot use such call And that's why for example in case of cascadebles I've moved such call into metadata class. In this particular place except the call for metadata itself we also access the metadata for the "most specific" class twice.
We already have the metadata from the context here - BeanMetaData<U> beanMetaData = valueContext.getCurrentBeanMetaData()
. But then in the loop for ( Class<? super U> clazz : beanMetaData.getClassHierarchy() )
we would get the beans class as a first element and try to get that same metadata again - BeanMetaData<? super U> hostingBeanMetaData = constraintMetaDataManager.getBeanMetaData( clazz );
Hence to skip this additional metadata retrieval I've broken the method into two parts - first one will work with the existing "current" metadata valueContext.getCurrentBeanMetaData()
and in the second part we iterate through the bean class hierarchy skipping the first element which is the bean class.
I also remember running JMH tests for these changes and the results were almost identical.
In the end this change gives us:
- one additional call to metadata manager removed ( as metadata for that class is already cached it's basically removing one Map access call). Which doesn't give us much in terms of perfomance
- as we don't call the metadata manager to get metadata at this point, the code is working for the property holders. Because we build the correct metadata for them in corresponding value contexts instead of building it here in the
ValidtorImpl
Now thinking about wrapping the String mappingName
or beans class into wrapper ... we might end up not needing this at all. As we would then, most likely, have the getClassHierarchy()
method return the wrappers. Or even maybe return the metadata right away, instead of simply returning classes.
for ( int i = 1; i < classHierarchy.size(); i++ ) { | ||
Class<? super U> clazz = classHierarchy.get( i ); | ||
|
||
BeanMetaData<? super U> hostingBeanMetaData = constraintMetaDataManager.getBeanMetaData( clazz ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for this part constraintMetaDataManager.getBeanMetaData( clazz );
I don't think that property holders should have anything in their "hierarchy", but if we decide that we want to have something like that we would either need to move out this entire method into the metadata impls (which doesn't feels right) or have two different ValidartorImpls
where we override this specific method to use either property holders or simple beans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree we can skip hierarchy for property holders.
Maybe there will be a use case at some point but let's wait to see how it is used.
From the notes that I've found on this I have:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Impressive work.
I added quite a lot of comments but most of them are minor.
Maybe we should discuss this container element issue live before applying these changes?
I already merged two of the changes. I think we could merge at least one more thing: the cascading changes which are applied to the existing stuff. That would get them out of the way. It's probably better that it's you extracting these as it's more complicated than the ones I extracted.
BTW, quick update: I think the review is best seen commit per commit instead of comment per comment. Otherwise, you will lose the logic of my comments.
Additional update: for the minor issues, we should probably use the "Resolve conversation" feature to keep track of things.
ValueContext<?, Object> newValueContext; | ||
Contracts.assertNotNull( value, "value cannot be null" ); | ||
newValueContext = ValueContext.getLocalExecutionContext( | ||
validatorScopedContext.getParameterNameProvider(), | ||
value, | ||
beanMetaDataManager.getBeanMetaData( value.getClass() ), | ||
cascadingMetaData.getBeanMetaDataForCascadable( beanMetaDataManager, value ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you expect to have the metadata for the free form case in the beanMetaDataManager
?
If not, I suspect we should push the beanMetaDataManager
to the constructor (and we would have 2 implementations of CascadingMetaData
in the end).
Not sure what you had in mind for that?
@@ -739,23 +739,14 @@ private void validateCascadedContainerElementsInContext(Object value, BaseBeanVa | |||
|
|||
private ValueContext<?, Object> buildNewLocalExecutionContext(ValueContext<?, ?> valueContext, Object value) { | |||
ValueContext<?, Object> newValueContext; | |||
if ( value != null ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I merged this one.
@@ -0,0 +1,113 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commit message: simple a implementation for Map
. I think the a
is misplaced.
) ); | ||
for ( PropertyAccessorCreator propertyAccessorCreator : propertyAccessorCreators ) { | ||
configuredPropertyCreators.add( propertyAccessorCreator ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make the set Immutable if we expect it to be.
throw LOG.getUnableToFindPropertyCreatorException( propertyHolderType ); | ||
} | ||
else if ( creators.size() > 1 ) { | ||
throw LOG.getUnableToFinUniquedPropertyCreatorException( propertyHolderType ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ToFinUniquedProperty -> ToFindUniquedProperty
|
||
/** | ||
* Constraint mapping creational context representing a type argument of a property, parameter or method return value | ||
* with a generic (return) type. Allows to place constraints on that type argument, mark it as cascadable and to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is far more restricted in the case of a property holder (e.g. no methods for instance)
* @author Gunnar Morling | ||
* @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI | ||
*/ | ||
public interface Cascadable<C extends Cascadable<C>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very generic. Maybe CascadableProperty
would be better?
* @author Gunnar Morling | ||
* @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI | ||
*/ | ||
public interface Constrainable<C extends Constrainable<C>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here about it being generic. Maybe ConstrainableProperty
would be better?
return new PropertyHolderPropertyConstraintLocationBuilder(); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it should be removed in the commit where it has been added? Or was it useful at the time?
@@ -14,7 +14,7 @@ | |||
import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; | |||
import org.hibernate.validator.internal.metadata.core.ConstraintHelper; | |||
import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; | |||
import org.hibernate.validator.internal.metadata.provider.PropertyHolderMetaDataProvider; | |||
import org.hibernate.validator.internal.metadata.provider.proeprtyholder.PropertyHolderMetaDataProvider; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a typo in the package name: it should be propertyholder.
Link to previous discussion on the same topic - #949
Note: This is still a work in progress.