/*
 * 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.cfg.beanvalidation;
import java.lang.annotation.ElementType;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.validation.Path;
import javax.validation.TraversableResolver;

import org.hibernate.Hibernate;
import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type;

Use Hibernate metadata to ignore cascade on entities. cascade on embeddable objects or collection of embeddable objects are accepted Also use Hibernate's native isInitialized method call.
Author:Emmanuel Bernard
/** * Use Hibernate metadata to ignore cascade on entities. * cascade on embeddable objects or collection of embeddable objects are accepted * * Also use Hibernate's native isInitialized method call. * * @author Emmanuel Bernard */
public class HibernateTraversableResolver implements TraversableResolver { private Set<String> associations; public HibernateTraversableResolver( EntityPersister persister, ConcurrentHashMap<EntityPersister, Set<String>> associationsPerEntityPersister, SessionFactoryImplementor factory) { this.associations = associationsPerEntityPersister.get( persister ); if (this.associations == null) { this.associations = new HashSet<String>(); addAssociationsToTheSetForAllProperties( persister.getPropertyNames(), persister.getPropertyTypes(), "", factory ); associationsPerEntityPersister.put( persister, associations ); } } private void addAssociationsToTheSetForAllProperties(String[] names, Type[] types, String prefix, SessionFactoryImplementor factory) { final int length = names.length; for( int index = 0 ; index < length; index++ ) { addAssociationsToTheSetForOneProperty( names[index], types[index], prefix, factory ); } } private void addAssociationsToTheSetForOneProperty(String name, Type type, String prefix, SessionFactoryImplementor factory) { if ( type.isCollectionType() ) { CollectionType collType = (CollectionType) type; Type assocType = collType.getElementType( factory ); addAssociationsToTheSetForOneProperty(name, assocType, prefix, factory); } //ToOne association else if ( type.isEntityType() || type.isAnyType() ) { associations.add( prefix + name ); } else if ( type.isComponentType() ) { CompositeType componentType = (CompositeType) type; addAssociationsToTheSetForAllProperties( componentType.getPropertyNames(), componentType.getSubtypes(), (prefix.equals( "" ) ? name : prefix + name) + ".", factory); } } private String getStringBasedPath(Path.Node traversableProperty, Path pathToTraversableObject) { StringBuilder path = new StringBuilder( ); for ( Path.Node node : pathToTraversableObject ) { if (node.getName() != null) { path.append( node.getName() ).append( "." ); } } if ( traversableProperty.getName() == null ) { throw new AssertionFailure( "TraversableResolver being passed a traversableProperty with null name. pathToTraversableObject: " + path.toString() ); } path.append( traversableProperty.getName() ); return path.toString(); } public boolean isReachable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) { //lazy, don't load return Hibernate.isInitialized( traversableObject ) && Hibernate.isPropertyInitialized( traversableObject, traversableProperty.getName() ); } public boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) { String path = getStringBasedPath( traversableProperty, pathToTraversableObject ); return ! associations.contains(path); } }