/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.engine.internal;

import java.util.Iterator;

import org.hibernate.HibernateException;
import org.hibernate.PropertyValueException;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

Implements the algorithm for validating property values for illegal null values
Author:Gavin King
/** * Implements the algorithm for validating property values for illegal null values * * @author Gavin King */
public final class Nullability { private final SharedSessionContractImplementor session; private final boolean checkNullability;
Constructs a Nullability
Params:
  • session – The session
/** * Constructs a Nullability * * @param session The session */
public Nullability(SharedSessionContractImplementor session) { this.session = session; this.checkNullability = session.getFactory().getSessionFactoryOptions().isCheckNullability(); }
Check nullability of the class persister properties
Params:
  • values – entity properties
  • persister – class persister
  • isUpdate – whether it is intended to be updated or saved
Throws:
/** * Check nullability of the class persister properties * * @param values entity properties * @param persister class persister * @param isUpdate whether it is intended to be updated or saved * * @throws PropertyValueException Break the nullability of one property * @throws HibernateException error while getting Component values */
public void checkNullability( final Object[] values, final EntityPersister persister, final boolean isUpdate) { checkNullability( values, persister, isUpdate ? NullabilityCheckType.UPDATE : NullabilityCheckType.CREATE ); } public enum NullabilityCheckType { CREATE, UPDATE, DELETE } public void checkNullability( final Object[] values, final EntityPersister persister, final NullabilityCheckType checkType) { /* * Typically when Bean Validation is on, we don't want to validate null values * at the Hibernate Core level. Hence the checkNullability setting. */ if ( checkNullability ) { /* * Algorithm * Check for any level one nullability breaks * Look at non null components to * recursively check next level of nullability breaks * Look at Collections containing components to * recursively check next level of nullability breaks * * * In the previous implementation, not-null stuffs where checked * filtering by level one only updateable * or insertable columns. So setting a sub component as update="false" * has no effect on not-null check if the main component had good checkeability * In this implementation, we keep this feature. * However, I never see any documentation mentioning that, but it's for * sure a limitation. */ final boolean[] nullability = persister.getPropertyNullability(); final boolean[] checkability = checkType == NullabilityCheckType.CREATE ? persister.getPropertyInsertability() : persister.getPropertyUpdateability(); final Type[] propertyTypes = persister.getPropertyTypes(); for ( int i = 0; i < values.length; i++ ) { if ( checkability[i] && values[i]!= LazyPropertyInitializer.UNFETCHED_PROPERTY ) { final Object value = values[i]; if ( !nullability[i] && value == null ) { //check basic level one nullablilty throw new PropertyValueException( "not-null property references a null or transient value", persister.getEntityName(), persister.getPropertyNames()[i] ); } else if ( value != null ) { //values is not null and is checkable, we'll look deeper final String breakProperties = checkSubElementsNullability( propertyTypes[i], value ); if ( breakProperties != null ) { throw new PropertyValueException( "not-null property references a null or transient value", persister.getEntityName(), buildPropertyPath( persister.getPropertyNames()[i], breakProperties ) ); } } } } } }
check sub elements-nullability. Returns property path that break nullability or null if none
Params:
  • propertyType – type to check
  • value – value to check
Throws:
Returns:property path
/** * check sub elements-nullability. Returns property path that break * nullability or null if none * * @param propertyType type to check * @param value value to check * * @return property path * @throws HibernateException error while getting subcomponent values */
private String checkSubElementsNullability(Type propertyType, Object value) throws HibernateException { if ( propertyType.isComponentType() ) { return checkComponentNullability( value, (CompositeType) propertyType ); } if ( propertyType.isCollectionType() ) { // persistent collections may have components final CollectionType collectionType = (CollectionType) propertyType; final Type collectionElementType = collectionType.getElementType( session.getFactory() ); if ( collectionElementType.isComponentType() ) { // check for all components values in the collection final CompositeType componentType = (CompositeType) collectionElementType; final Iterator itr = CascadingActions.getLoadedElementsIterator( session, collectionType, value ); while ( itr.hasNext() ) { final Object compositeElement = itr.next(); if ( compositeElement != null ) { return checkComponentNullability( compositeElement, componentType ); } } } } return null; }
check component nullability. Returns property path that break nullability or null if none
Params:
  • value – component properties
  • compositeType – component not-nullable type
Throws:
Returns:property path
/** * check component nullability. Returns property path that break * nullability or null if none * * @param value component properties * @param compositeType component not-nullable type * * @return property path * @throws HibernateException error while getting subcomponent values */
private String checkComponentNullability(Object value, CompositeType compositeType) throws HibernateException { // IMPL NOTE : we currently skip checking "any" and "many to any" mappings. // // This is not the best solution. But atm there is a mismatch between AnyType#getPropertyNullability // and the fact that cascaded-saves for "many to any" mappings are not performed until after this nullability // check. So the nullability check fails for transient entity elements with generated identifiers because // the identifier is not yet generated/assigned (is null) // // The more correct fix would be to cascade saves of the many-to-any elements before the Nullability checking if ( compositeType.isAnyType() ) { return null; } final boolean[] nullability = compositeType.getPropertyNullability(); if ( nullability != null ) { //do the test final Object[] subValues = compositeType.getPropertyValues( value, session ); final Type[] propertyTypes = compositeType.getSubtypes(); for ( int i = 0; i < subValues.length; i++ ) { final Object subValue = subValues[i]; if ( !nullability[i] && subValue==null ) { return compositeType.getPropertyNames()[i]; } else if ( subValue != null ) { final String breakProperties = checkSubElementsNullability( propertyTypes[i], subValue ); if ( breakProperties != null ) { return buildPropertyPath( compositeType.getPropertyNames()[i], breakProperties ); } } } } return null; }
Return a well formed property path. Basically, it will return parent.child
Params:
  • parent – parent in path
  • child – child in path
Returns:parent-child path
/** * Return a well formed property path. Basically, it will return parent.child * * @param parent parent in path * @param child child in path * * @return parent-child path */
private static String buildPropertyPath(String parent, String child) { return parent + '.' + child; } }