Skip to content
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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

marko-bekhta
Copy link
Member

Link to previous discussion on the same topic - #949

Note: This is still a work in progress.

…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
@@ -739,23 +739,14 @@ private void validateCascadedContainerElementsInContext(Object value, BaseBeanVa

private ValueContext<?, Object> buildNewLocalExecutionContext(ValueContext<?, ?> valueContext, Object value) {
ValueContext<?, Object> newValueContext;
if ( value != null ) {
Copy link
Member Author

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.

Copy link
Member

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 {
Copy link
Member Author

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;
Copy link
Member Author

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.

Copy link
Member

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;
Copy link
Member Author

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();
Copy link
Member Author

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.

Copy link
Member

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).

Copy link
Member Author

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 );
Copy link
Member Author

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.

Copy link
Member

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.

@marko-bekhta
Copy link
Member Author

From the notes that I've found on this I have:

  • need to make BeanMetaData abstract. There's a comment about it above. There will be two impls one for regular bean and another for property holder.
  • Store simple constraint defs instead of Set<MetaConstraintBuilder<?>> constraints in programmatic API
  • figure out how users will provide "type argument" constraints.

Copy link
Member

@gsmet gsmet left a 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 ),
Copy link
Member

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 ) {
Copy link
Member

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 @@
/*
Copy link
Member

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 );
}
Copy link
Member

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 );
Copy link
Member

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
Copy link
Member

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 &lt;kevin.pollet@serli.com&gt; (C) 2011 SERLI
*/
public interface Cascadable<C extends Cascadable<C>> {
Copy link
Member

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 &lt;kevin.pollet@serli.com&gt; (C) 2011 SERLI
*/
public interface Constrainable<C extends Constrainable<C>> {
Copy link
Member

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();
}
}

Copy link
Member

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;
Copy link
Member

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.

@gsmet gsmet mentioned this pull request Nov 7, 2018
Base automatically changed from master to main March 19, 2021 08:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants