package org.hibernate.engine.internal;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import org.hibernate.HibernateException;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.spi.EventSource;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.Type;
public final class Cascade {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( Cascade.class );
private Cascade() {
}
public static void cascade(
final CascadingAction action, final CascadePoint cascadePoint,
final EventSource eventSource, final EntityPersister persister, final Object parent)
throws HibernateException {
cascade( action, cascadePoint, eventSource, persister, parent, null );
}
public static void cascade(
final CascadingAction action,
final CascadePoint cascadePoint,
final EventSource eventSource,
final EntityPersister persister,
final Object parent,
final Object anything) throws HibernateException {
if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) {
final boolean traceEnabled = LOG.isTraceEnabled();
if ( traceEnabled ) {
LOG.tracev( "Processing cascade {0} for: {1}", action, persister.getEntityName() );
}
final Type[] types = persister.getPropertyTypes();
final String[] propertyNames = persister.getPropertyNames();
final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles();
final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent );
final int componentPathStackDepth = 0;
for ( int i = 0; i < types.length; i++) {
final CascadeStyle style = cascadeStyles[ i ];
final String propertyName = propertyNames[ i ];
final boolean isUninitializedProperty =
hasUninitializedLazyProperties &&
!persister.getInstrumentationMetadata().isAttributeLoaded( parent, propertyName );
if ( style.doCascade( action ) ) {
final Object child;
if ( isUninitializedProperty ) {
if ( types[ i ].isCollectionType() ) {
child = types[ i ].resolve( LazyPropertyInitializer.UNFETCHED_PROPERTY, eventSource, parent );
}
else if ( action.performOnLazyProperty() ) {
LazyAttributeLoadingInterceptor interceptor = persister.getInstrumentationMetadata().extractInterceptor( parent );
child = interceptor.fetchAttribute( parent, propertyName );
}
else {
continue;
}
}
else {
child = persister.getPropertyValue( parent, i );
}
cascadeProperty(
action,
cascadePoint,
eventSource,
componentPathStackDepth,
parent,
child,
types[ i ],
style,
propertyName,
anything,
false
);
}
else {
if ( action.requiresNoCascadeChecking() ) {
action.noCascade(
eventSource,
parent,
persister,
types[i],
i
);
}
if ( action.deleteOrphans() && !isUninitializedProperty ) {
cascadeLogicalOneToOneOrphanRemoval(
action,
eventSource,
componentPathStackDepth,
parent,
persister.getPropertyValue( parent, i ),
types[ i ],
style,
propertyName,
false
);
}
}
}
if ( traceEnabled ) {
LOG.tracev( "Done processing cascade {0} for: {1}", action, persister.getEntityName() );
}
}
}
private static void cascadeProperty(
final CascadingAction action,
final CascadePoint cascadePoint,
final EventSource eventSource,
final int componentPathStackDepth,
final Object parent,
final Object child,
final Type type,
final CascadeStyle style,
final String propertyName,
final Object anything,
final boolean isCascadeDeleteEnabled) throws HibernateException {
if ( child != null ) {
if ( type.isAssociationType() ) {
final AssociationType associationType = (AssociationType) type;
if ( cascadeAssociationNow( cascadePoint, associationType ) ) {
cascadeAssociation(
action,
cascadePoint,
eventSource,
componentPathStackDepth,
parent,
child,
type,
style,
anything,
isCascadeDeleteEnabled
);
}
}
else if ( type.isComponentType() ) {
cascadeComponent(
action,
cascadePoint,
eventSource,
componentPathStackDepth,
parent,
child,
(CompositeType) type,
anything
);
}
}
cascadeLogicalOneToOneOrphanRemoval(
action,
eventSource,
componentPathStackDepth,
parent,
child,
type,
style,
propertyName,
isCascadeDeleteEnabled );
}
private static void cascadeLogicalOneToOneOrphanRemoval(
final CascadingAction action,
final EventSource eventSource,
final int componentPathStackDepth,
final Object parent,
final Object child,
final Type type,
final CascadeStyle style,
final String propertyName,
final boolean isCascadeDeleteEnabled) throws HibernateException {
if ( isLogicalOneToOne( type ) ) {
if ( style.hasOrphanDelete() && action.deleteOrphans() ) {
final EntityEntry entry = eventSource.getPersistenceContext().getEntry( parent );
if ( entry != null && entry.getStatus() != Status.SAVING ) {
Object loadedValue;
if ( componentPathStackDepth == 0 ) {
loadedValue = entry.getLoadedValue( propertyName );
}
else {
loadedValue = null;
}
if ( child == null || ( loadedValue != null && child != loadedValue ) ) {
EntityEntry valueEntry = eventSource
.getPersistenceContext().getEntry(
loadedValue );
if ( valueEntry == null && loadedValue instanceof HibernateProxy ) {
loadedValue = eventSource.getPersistenceContext().unproxyAndReassociate( loadedValue );
valueEntry = eventSource.getPersistenceContext().getEntry( loadedValue );
if ( child == loadedValue ) {
return;
}
}
if ( valueEntry != null ) {
final String entityName = valueEntry.getPersister().getEntityName();
if ( LOG.isTraceEnabled() ) {
final Serializable id = valueEntry.getPersister().getIdentifier( loadedValue, eventSource );
final String description = MessageHelper.infoString( entityName, id );
LOG.tracev( "Deleting orphaned entity instance: {0}", description );
}
if ( type.isAssociationType() && ( (AssociationType) type ).getForeignKeyDirection().equals(
ForeignKeyDirection.TO_PARENT
) ) {
eventSource.removeOrphanBeforeUpdates( entityName, loadedValue );
}
else {
eventSource.delete( entityName, loadedValue, isCascadeDeleteEnabled, new HashSet() );
}
}
}
}
}
}
}
private static boolean isLogicalOneToOne(Type type) {
return type.isEntityType() && ( (EntityType) type ).isLogicalOneToOne();
}
private static boolean cascadeAssociationNow(final CascadePoint cascadePoint, AssociationType associationType) {
return associationType.getForeignKeyDirection().cascadeNow( cascadePoint );
}
private static void cascadeComponent(
final CascadingAction action,
final CascadePoint cascadePoint,
final EventSource eventSource,
final int componentPathStackDepth,
final Object parent,
final Object child,
final CompositeType componentType,
final Object anything) {
Object[] children = null;
final Type[] types = componentType.getSubtypes();
final String[] propertyNames = componentType.getPropertyNames();
for ( int i = 0; i < types.length; i++ ) {
final CascadeStyle componentPropertyStyle = componentType.getCascadeStyle( i );
final String subPropertyName = propertyNames[i];
if ( componentPropertyStyle.doCascade( action ) ) {
if (children == null) {
children = componentType.getPropertyValues( child, eventSource );
}
cascadeProperty(
action,
cascadePoint,
eventSource,
componentPathStackDepth + 1,
parent,
children[i],
types[i],
componentPropertyStyle,
subPropertyName,
anything,
false
);
}
}
}
private static void cascadeAssociation(
final CascadingAction action,
final CascadePoint cascadePoint,
final EventSource eventSource,
final int componentPathStackDepth,
final Object parent,
final Object child,
final Type type,
final CascadeStyle style,
final Object anything,
final boolean isCascadeDeleteEnabled) {
if ( type.isEntityType() || type.isAnyType() ) {
cascadeToOne( action, eventSource, parent, child, type, style, anything, isCascadeDeleteEnabled );
}
else if ( type.isCollectionType() ) {
cascadeCollection(
action,
cascadePoint,
eventSource,
componentPathStackDepth,
parent,
child,
style,
anything,
(CollectionType) type
);
}
}
private static void cascadeCollection(
final CascadingAction action,
final CascadePoint cascadePoint,
final EventSource eventSource,
final int componentPathStackDepth,
final Object parent,
final Object child,
final CascadeStyle style,
final Object anything,
final CollectionType type) {
final CollectionPersister persister = eventSource.getFactory().getCollectionPersister( type.getRole() );
final Type elemType = persister.getElementType();
CascadePoint elementsCascadePoint = cascadePoint;
if ( cascadePoint == CascadePoint.AFTER_INSERT_BEFORE_DELETE ) {
elementsCascadePoint = CascadePoint.AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION;
}
if ( elemType.isEntityType() || elemType.isAnyType() || elemType.isComponentType() ) {
cascadeCollectionElements(
action,
elementsCascadePoint,
eventSource,
componentPathStackDepth,
parent,
child,
type,
style,
elemType,
anything,
persister.isCascadeDeleteEnabled()
);
}
}
private static void cascadeToOne(
final CascadingAction action,
final EventSource eventSource,
final Object parent,
final Object child,
final Type type,
final CascadeStyle style,
final Object anything,
final boolean isCascadeDeleteEnabled) {
final String entityName = type.isEntityType()
? ( (EntityType) type ).getAssociatedEntityName()
: null;
if ( style.reallyDoCascade( action ) ) {
eventSource.getPersistenceContext().addChildParent( child, parent );
try {
action.cascade( eventSource, child, entityName, anything, isCascadeDeleteEnabled );
}
finally {
eventSource.getPersistenceContext().removeChildParent( child );
}
}
}
private static void cascadeCollectionElements(
final CascadingAction action,
final CascadePoint cascadePoint,
final EventSource eventSource,
final int componentPathStackDepth,
final Object parent,
final Object child,
final CollectionType collectionType,
final CascadeStyle style,
final Type elemType,
final Object anything,
final boolean isCascadeDeleteEnabled) throws HibernateException {
final boolean reallyDoCascade = style.reallyDoCascade( action ) && child != CollectionType.UNFETCHED_COLLECTION;
if ( reallyDoCascade ) {
final boolean traceEnabled = LOG.isTraceEnabled();
if ( traceEnabled ) {
LOG.tracev( "Cascade {0} for collection: {1}", action, collectionType.getRole() );
}
final Iterator itr = action.getCascadableChildrenIterator( eventSource, collectionType, child );
while ( itr.hasNext() ) {
cascadeProperty(
action,
cascadePoint,
eventSource,
componentPathStackDepth,
parent,
itr.next(),
elemType,
style,
null,
anything,
isCascadeDeleteEnabled
);
}
if ( traceEnabled ) {
LOG.tracev( "Done cascade {0} for collection: {1}", action, collectionType.getRole() );
}
}
final boolean deleteOrphans = style.hasOrphanDelete()
&& action.deleteOrphans()
&& elemType.isEntityType()
&& child instanceof PersistentCollection;
if ( deleteOrphans ) {
final boolean traceEnabled = LOG.isTraceEnabled();
if ( traceEnabled ) {
LOG.tracev( "Deleting orphans for collection: {0}", collectionType.getRole() );
}
final String entityName = collectionType.getAssociatedEntityName( eventSource.getFactory() );
deleteOrphans( eventSource, entityName, (PersistentCollection) child );
if ( traceEnabled ) {
LOG.tracev( "Done deleting orphans for collection: {0}", collectionType.getRole() );
}
}
}
private static void deleteOrphans(EventSource eventSource, String entityName, PersistentCollection pc) throws HibernateException {
final Collection orphans;
if ( pc.wasInitialized() ) {
final CollectionEntry ce = eventSource.getPersistenceContext().getCollectionEntry( pc );
orphans = ce==null
? java.util.Collections.EMPTY_LIST
: ce.getOrphans( entityName, pc );
}
else {
orphans = pc.getQueuedOrphans( entityName );
}
for ( Object orphan : orphans ) {
if ( orphan != null ) {
LOG.tracev( "Deleting orphaned entity instance: {0}", entityName );
eventSource.delete( entityName, orphan, false, new HashSet() );
}
}
}
}