diff --git a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java index bd5d3273add5..68fd32e38beb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java @@ -5,8 +5,11 @@ package org.hibernate.boot.beanvalidation; import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.invoke.MethodHandles; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; @@ -187,12 +190,23 @@ public static void applyRelationalConstraints( final Class[] groupsArray = buildGroupsForOperation( GroupsPerOperation.Operation.DDL, settings, classLoaderAccess ); final Set> groups = new HashSet<>( asList( groupsArray ) ); + final Map, Boolean> constraintCompositionTypeCache = new HashMap<>(); + for ( PersistentClass persistentClass : persistentClasses ) { final String className = persistentClass.getClassName(); if ( isNotEmpty( className ) ) { final Class clazz = entityClass( classLoaderAccess, className ); try { - applyDDL( "", persistentClass, clazz, factory, groups, true, dialect ); + applyDDL( + "", + persistentClass, + clazz, + factory, + groups, + true, + dialect, + constraintCompositionTypeCache + ); } catch (Exception e) { LOG.unableToApplyConstraints( className, e ); @@ -217,7 +231,9 @@ private static void applyDDL( ValidatorFactory factory, Set> groups, boolean activateNotNull, - Dialect dialect) { + Dialect dialect, + Map, Boolean> constraintCompositionTypeCache + ) { final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass( clazz ); //cno bean level constraints can be applied, go to the properties for ( PropertyDescriptor propertyDesc : descriptor.getConstrainedProperties() ) { @@ -230,7 +246,9 @@ private static void applyDDL( propertyDesc, groups, activateNotNull, - dialect + false, + dialect, + constraintCompositionTypeCache ); if ( property.isComposite() && propertyDesc.isCascaded() ) { final Component component = (Component) property.getValue(); @@ -244,7 +262,8 @@ private static void applyDDL( // activate not null and if the property is not null. // Otherwise, all sub columns should be left nullable activateNotNull && hasNotNull, - dialect + dialect, + constraintCompositionTypeCache ); } } @@ -257,12 +276,18 @@ private static boolean applyConstraints( PropertyDescriptor propertyDesc, Set> groups, boolean canApplyNotNull, - Dialect dialect) { - boolean hasNotNull = false; + boolean useOrLogicForComposedConstraint, + Dialect dialect, + Map, Boolean> constraintCompositionTypeCache) { + + boolean firstItem = true; + boolean composedResultHasNotNull = false; for ( ConstraintDescriptor descriptor : constraintDescriptors ) { + boolean hasNotNull = false; + if ( groups == null || !disjoint( descriptor.getGroups(), groups ) ) { if ( canApplyNotNull ) { - hasNotNull = hasNotNull || applyNotNull( property, descriptor ); + hasNotNull = isNotNullDescriptor( descriptor ); } // apply bean validation specific constraints @@ -276,19 +301,70 @@ private static boolean applyConstraints( // will be taken care later. applyLength( property, descriptor, propertyDesc ); - // pass an empty set as composing constraints inherit the main constraint and thus are matching already - final boolean hasNotNullFromComposingConstraints = applyConstraints( - descriptor.getComposingConstraints(), - property, propertyDesc, null, - canApplyNotNull, - dialect - ); + // Composing constraints + if ( !descriptor.getComposingConstraints().isEmpty() ) { + // pass an empty set as composing constraints inherit the main constraint and thus are matching already + final boolean hasNotNullFromComposingConstraints = applyConstraints( + descriptor.getComposingConstraints(), + property, propertyDesc, null, + canApplyNotNull, + isConstraintCompositionOfTypeOr( descriptor, constraintCompositionTypeCache ), + dialect, + constraintCompositionTypeCache + ); + hasNotNull |= hasNotNullFromComposingConstraints; + } + } - hasNotNull = hasNotNull || hasNotNullFromComposingConstraints; + if ( firstItem ) { + composedResultHasNotNull = hasNotNull; + firstItem = false; } + else if ( !useOrLogicForComposedConstraint ) { + // If the constraint composition is of type AND (default) then only ONE constraint needs to + // be non-nullable for the property to be marked as 'not-null'. + composedResultHasNotNull |= hasNotNull; + } + else { + // If the constraint composition is of type OR then ALL constraints need to + // be non-nullable for the property to be marked as 'not-null'. + composedResultHasNotNull &= hasNotNull; + } + } + if ( composedResultHasNotNull ) { + markNotNull( property ); } - return hasNotNull; + + return composedResultHasNotNull; + } + + private static boolean isConstraintCompositionOfTypeOr( + ConstraintDescriptor descriptor, + Map, Boolean> constraintCompositionTypeCache + ) { + if ( descriptor.getComposingConstraints().size() < 2 ) { + return false; + } + + final Class composedAnnotation = descriptor.getAnnotation().annotationType(); + return constraintCompositionTypeCache.computeIfAbsent( composedAnnotation, value -> { + for ( Annotation annotation : value.getAnnotations() ) { + if ( "org.hibernate.validator.constraints.ConstraintComposition" + .equals( annotation.annotationType().getName() ) ) { + try { + Method valueMethod = annotation.annotationType().getMethod( "value" ); + Object result = valueMethod.invoke( annotation ); + return result != null && "OR".equals( result.toString() ); + } + catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException ex ) { + LOG.debug( "ConstraintComposition type could not be determined. Assuming AND", ex ); + return false; + } + } + } + return false; + }); } private static void applyMin(Property property, ConstraintDescriptor descriptor, Dialect dialect) { @@ -328,35 +404,33 @@ private static void applySQLCheck(Column column, String checkConstraint) { column.addCheckConstraint( new CheckConstraint( checkConstraint ) ); } - private static boolean applyNotNull(Property property, ConstraintDescriptor descriptor) { - boolean hasNotNull = false; - // NotNull, NotEmpty, and NotBlank annotation add not-null on column + private static boolean isNotNullDescriptor(ConstraintDescriptor descriptor) { final Class annotationType = descriptor.getAnnotation().annotationType(); - if ( NotNull.class.equals(annotationType) + return NotNull.class.equals(annotationType) || NotEmpty.class.equals(annotationType) - || NotBlank.class.equals(annotationType)) { - // single table inheritance should not be forced to null due to shared state - if ( !( property.getPersistentClass() instanceof SingleTableSubclass ) ) { - // composite should not add not-null on all columns - if ( !property.isComposite() ) { - for ( Selectable selectable : property.getSelectables() ) { - if ( selectable instanceof Column column ) { - column.setNullable( false ); - } - else { - LOG.debugf( - "@NotNull was applied to attribute [%s] which is defined (at least partially) " + - "by formula(s); formula portions will be skipped", - property.getName() - ); - } + || NotBlank.class.equals(annotationType); + } + + private static void markNotNull(Property property) { + // single table inheritance should not be forced to null due to shared state + if ( !( property.getPersistentClass() instanceof SingleTableSubclass ) ) { + // composite should not add not-null on all columns + if ( !property.isComposite() ) { + for ( Selectable selectable : property.getSelectables() ) { + if ( selectable instanceof Column column ) { + column.setNullable( false ); + } + else { + LOG.debugf( + "@NotNull was applied to attribute [%s] which is defined (at least partially) " + + "by formula(s); formula portions will be skipped", + property.getName() + ); } } } - hasNotNull = true; } - property.setOptional( !hasNotNull ); - return hasNotNull; + property.setOptional( false ); } private static void applyDigits(Property property, ConstraintDescriptor descriptor) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/Address.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/Address.java index 2e92879123ee..6d41d36f589b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/Address.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/Address.java @@ -15,6 +15,13 @@ public class Address { private String line1; private String line2; + private String line3; + private String line4; + private String line5; + private String line6; + private String line7; + private String line8; + private String line9; private String zip; private String state; @Size(max = 20) @@ -52,6 +59,69 @@ public void setLine2(String line2) { this.line2 = line2; } + @CustomNullOrNotBlank + public String getLine3() { + return line3; + } + + public void setLine3(String line3) { + this.line3 = line3; + } + + @CustomNotNullOrNotBlank + public String getLine4() { + return line4; + } + + public void setLine4(String line4) { + this.line4 = line4; + } + + @CustomNullAndNotBlank + public String getLine5() { + return line5; + } + + public void setLine5(String line5) { + this.line5 = line5; + } + + @CustomNotNullAndNotBlank + public String getLine6() { + return line6; + } + + public void setLine6(String line6) { + this.line6 = line6; + } + + @CustomNullOrPattern + public String getLine7() { + return line7; + } + + public void setLine7(String line7) { + this.line7 = line7; + } + + @CustomNotNull + public String getLine8() { + return line8; + } + + public void setLine8(String line8) { + this.line8 = line8; + } + + @CustomNull + public String getLine9() { + return line9; + } + + public void setLine9(String line9) { + this.line9 = line9; + } + @Size(max = 3) @NotNull public String getState() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNull.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNull.java new file mode 100644 index 000000000000..b2b09e5f84be --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNull.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.beanvalidation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.constraints.NotNull; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Used to test constraint composition. + */ +@NotNull +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +public @interface CustomNotNull { + + String message() default "{org.hibernate.validator.constraints.CustomNotNull.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNullAndNotBlank.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNullAndNotBlank.java new file mode 100644 index 000000000000..d2769fabbd4b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNullAndNotBlank.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.beanvalidation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.ReportAsSingleViolation; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.constraints.ConstraintComposition; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.hibernate.validator.constraints.CompositionType.AND; + +/** + * Used to test constraint composition with AND for nullability of columns. + */ +@ConstraintComposition(AND) +@NotNull +@NotBlank +@ReportAsSingleViolation +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +public @interface CustomNotNullAndNotBlank { + + String message() default "{org.hibernate.validator.constraints.CustomNotNullAndNotBlank.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNullOrNotBlank.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNullOrNotBlank.java new file mode 100644 index 000000000000..3083505ef067 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNotNullOrNotBlank.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.beanvalidation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.ReportAsSingleViolation; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.constraints.ConstraintComposition; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.hibernate.validator.constraints.CompositionType.OR; + +/** + * Used to test constraint composition with OR for nullability of columns. + */ +@ConstraintComposition(OR) +@NotNull +@NotBlank +@ReportAsSingleViolation +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +public @interface CustomNotNullOrNotBlank { + + String message() default "{org.hibernate.validator.constraints.CustomNotNullOrNotBlank.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNull.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNull.java new file mode 100644 index 000000000000..8813d6a8730e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNull.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.beanvalidation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.constraints.Null; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Used to test constraint composition. + */ +@Null +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +public @interface CustomNull { + + String message() default "{org.hibernate.validator.constraints.CustomNull.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullAndNotBlank.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullAndNotBlank.java new file mode 100644 index 000000000000..e9676525ee4b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullAndNotBlank.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.beanvalidation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.ReportAsSingleViolation; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Null; +import org.hibernate.validator.constraints.ConstraintComposition; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.hibernate.validator.constraints.CompositionType.AND; + +/** + * Used to test constraint composition with AND for nullability of columns. + */ +@ConstraintComposition(AND) +@Null +@NotBlank +@ReportAsSingleViolation +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +public @interface CustomNullAndNotBlank { + + String message() default "{org.hibernate.validator.constraints.CustomNullAndNotBlank.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullOrNotBlank.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullOrNotBlank.java new file mode 100644 index 000000000000..51239913de4e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullOrNotBlank.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.beanvalidation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.ReportAsSingleViolation; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Null; +import org.hibernate.validator.constraints.ConstraintComposition; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.hibernate.validator.constraints.CompositionType.OR; + +/** + * Used to test constraint composition with OR for nullability of columns. + */ +@ConstraintComposition(OR) +@Null +@NotBlank +@ReportAsSingleViolation +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +public @interface CustomNullOrNotBlank { + + String message() default "{org.hibernate.validator.constraints.CustomNullOrNotBlank.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullOrPattern.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullOrPattern.java new file mode 100644 index 000000000000..c84fa981eb22 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CustomNullOrPattern.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.beanvalidation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.ReportAsSingleViolation; +import jakarta.validation.constraints.Null; +import jakarta.validation.constraints.Pattern; +import org.hibernate.validator.constraints.ConstraintComposition; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.hibernate.validator.constraints.CompositionType.OR; + +/** + * Used to test constraint composition with OR for nullability of columns. + */ +@ConstraintComposition(OR) +@Null +@Pattern( regexp = ".*" ) +@ReportAsSingleViolation +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +public @interface CustomNullOrPattern { + + String message() default "{org.hibernate.validator.constraints.CustomNullOrPattern.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/DDLTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/DDLTest.java index 77cd87c3ebd3..fcea4da20526 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/DDLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/DDLTest.java @@ -34,7 +34,6 @@ public void testBasicDDL() { assertFalse( zipColumn.isNullable() ); } - @Test public void testNotNullDDL() { PersistentClass classMapping = metadata().getEntityBinding( Address.class.getName() ); @@ -46,6 +45,41 @@ public void testNotNullDDL() { Column line2Column = classMapping.getProperty( "line2" ).getColumns().get(0); assertFalse("Validator annotations are applied on line2 as it is @NotBlank", line2Column.isNullable()); + + Column line3Column = classMapping.getProperty( "line3" ).getColumns().get(0); + assertTrue( + "Validator composition of type OR should result in line3 being nullable", + line3Column.isNullable()); + + Column line4Column = classMapping.getProperty( "line4" ).getColumns().get(0); + assertFalse( + "Validator composition of type OR should result in line4 being not-null", + line4Column.isNullable()); + + Column line5Column = classMapping.getProperty( "line5" ).getColumns().get(0); + assertFalse( + "Validator composition of type AND should result in line5 being not-null", + line5Column.isNullable()); + + Column line6Column = classMapping.getProperty( "line6" ).getColumns().get(0); + assertFalse( + "Validator composition of type AND should result in line6 being not-null", + line6Column.isNullable()); + + Column line7Column = classMapping.getProperty( "line7" ).getColumns().get(0); + assertTrue( + "Validator composition of type OR should result in line7 being nullable", + line7Column.isNullable()); + + Column line8Column = classMapping.getProperty( "line8" ).getColumns().get( 0 ); + assertFalse( + "Validator should result in line8 being not-null", + line8Column.isNullable()); + + Column line9Column = classMapping.getProperty( "line9" ).getColumns().get( 0 ); + assertTrue( + "Validator should result in line9 being nullable", + line9Column.isNullable()); } @Test