//$Id: Cascades.java,v 1.9 2003/03/24 10:29:01 oneovthafew Exp $
package cirrus.hibernate.engine;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import cirrus.hibernate.HibernateException;
import cirrus.hibernate.Session;
import cirrus.hibernate.collections.PersistentCollection;
import cirrus.hibernate.impl.CollectionPersister;
import cirrus.hibernate.persister.ClassPersister;
import cirrus.hibernate.proxy.HibernateProxy;
import cirrus.hibernate.proxy.HibernateProxyHelper;
import cirrus.hibernate.type.AbstractComponentType;
import cirrus.hibernate.type.AssociationType;
import cirrus.hibernate.type.PersistentCollectionType;
import cirrus.hibernate.type.Type;
/**
* Implements cascaded save / delete / update
* @see cirrus.hibernate.type.AssociationType
*/
public final class Cascades {
private static final Log log = LogFactory.getLog(Cascades.class);
// The available cascade actions:
/**
* A session action that may be cascaded from parent entity to its children
*/
public static abstract class CascadingAction {
protected CascadingAction() {}
/**
* cascade the action to the child object
*/
abstract void cascade(Session session, Object child) throws SQLException, HibernateException;
/**
* Should this action be cascaded to the given (possibly uninitialized) collection?
*/
abstract boolean shouldCascadeCollection(Object collection);
}
/**
* @see cirrus.hibernate.Session#delete(Object)
*/
public static final CascadingAction ACTION_DELETE = new CascadingAction() {
void cascade(Session session, Object child) throws SQLException, HibernateException {
log.trace("cascading to delete()");
session.delete(child);
}
boolean shouldCascadeCollection(Object collection) {
return true;
}
};
/**
* @see cirrus.hibernate.Session#saveOrUpdate(Object)
*/
public static final CascadingAction ACTION_SAVE_UPDATE = new CascadingAction() {
void cascade(Session session, Object child) throws SQLException, HibernateException {
if (
!(child instanceof HibernateProxy) ||
!HibernateProxyHelper.getLazyInitializer( (HibernateProxy) child ).isUninitialized()
// saves / updates don't cascade to uninitialized proxies
) {
log.trace("cascading to saveOrUpdate()");
session.saveOrUpdate(child);
}
}
boolean shouldCascadeCollection(Object collection) {
return !(collection instanceof PersistentCollection) || ( (PersistentCollection) collection ).wasInitialized();
// saves / updates don't cascade to uninitialized collections
}
};
// The types of children to cascade to:
/**
* A cascade point that occurs just after the insertion of the parent entity and
* just before deletion
*/
public static final int CASCADE_AFTER_INSERT_BEFORE_DELETE = 1;
/**
* A cascade point that occurs just before the insertion of the parent entity and
* just after deletion
*/
public static final int CASCADE_BEFORE_INSERT_AFTER_DELETE = 2;
/**
* A cascade point that occurs just after the insertion of the parent entity and
* just before deletion, inside a collection
*/
public static final int CASCADE_AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION = 3;
/**
* A cascade point that occurs just after the update of the parent entity
*/
public static final int CASCADE_ON_UPDATE = 0;
// The allowable cascade styles for a property:
/**
* A style of cascade that can be specified by the mapping for an association.
* The style is specified by the <tt>cascade</tt> attribute in the mapping file.
*/
public static abstract class CascadeStyle {
protected CascadeStyle() {}
/**
* Sould the given action be cascaded?
*/
abstract boolean doCascade(CascadingAction action);
};
/**
* save / delete / update
*/
public static final CascadeStyle STYLE_ALL = new CascadeStyle() {
boolean doCascade(CascadingAction action) {
return true;
}
};
/**
* save / update
*/
public static final CascadeStyle STYLE_EXCEPT_DELETE = new CascadeStyle() {
boolean doCascade(CascadingAction action) {
return action!=ACTION_DELETE;
}
};
/**
* delete
*/
public static final CascadeStyle STYLE_ONLY_DELETE = new CascadeStyle() {
boolean doCascade(CascadingAction action) {
return action==ACTION_DELETE;
}
};
/**
* no cascades
*/
public static final CascadeStyle STYLE_NONE = new CascadeStyle() {
boolean doCascade(CascadingAction action) {
return false;
}
};
// The allowable unsaved-value settings:
/**
* A strategy for determining if an identifier value is an identifier of
* a new transient instance or a previously persistent transient instance.
* The strategy is determined by the <tt>unsaved-value</tt> attribute in
* the mapping file.
*/
public static class IdentifierValue {
private final Object value;
protected IdentifierValue() {
this.value = null;
}
/**
* Assume the transient instance is newly instantiated if
* its identifier is null or equal to <tt>value</tt>
*/
public IdentifierValue(Object value) {
this.value = value;
}
/**
* Does the given identifier belong to a new instance?
*/
public boolean isUnsaved(Serializable id) {
return id==null || value.equals(id);
}
}
/**
* Always assume the transient instance is newly instantiated
*/
public static final IdentifierValue SAVE_ANY = new IdentifierValue() {
public final boolean isUnsaved(Serializable id) {
return true;
}
};
/**
* Never assume the transient instance is newly instantiated
*/
public static final IdentifierValue SAVE_NONE = new IdentifierValue() {
public final boolean isUnsaved(Serializable id) {
return false;
}
};
/**
* Assume the transient instance is newly instantiated if the identifier
* is null.
*/
public static final IdentifierValue SAVE_NULL = new IdentifierValue() {
public final boolean isUnsaved(Serializable id) {
return id==null;
}
};
/**
* Cascade an action to the child
*/
private static void cascade(SessionImplementor session, Object child, Type type, CascadingAction action, int cascadeTo) throws SQLException, HibernateException {
if (child!=null) {
if ( type.isAssociationType() ) {
if ( ( (AssociationType) type ).getForeignKeyType().cascadeNow(cascadeTo) ) {
if ( type.isEntityType() ) {
action.cascade(session, child);
}
else if ( type.isPersistentCollectionType() ) {
final int cascadeVia;
if ( cascadeTo==CASCADE_AFTER_INSERT_BEFORE_DELETE) {
cascadeVia = CASCADE_AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION;
}
else {
cascadeVia = cascadeTo;
}
PersistentCollectionType pctype = (PersistentCollectionType) type;
CollectionPersister persister = session.getFactory().getCollectionPersister( pctype.getRole() );
Type elemType = persister.getElementType();
Iterator iter;
if ( action.shouldCascadeCollection(child) ) {
if ( log.isTraceEnabled() ) log.trace( "cascading to collection: " + pctype.getRole() );
iter = pctype.getElementsIterator(child);
}
else {
if (child instanceof PersistentCollection ) {
PersistentCollection pc = (PersistentCollection) child;
if ( pc.hasQueuedAdds() ) {
iter = pc.queuedAddsIterator();
}
else {
iter = null;
}
}
else {
iter = null;
}
}
if (iter!=null) {
while ( iter.hasNext() ) cascade( session, iter.next(), elemType, action, cascadeVia );
}
}
}
}
else if ( type.isComponentType() ) {
AbstractComponentType ctype = ( (AbstractComponentType) type );
Object[] children = ctype.getPropertyValues(child);
Type[] types = ctype.getSubtypes();
for ( int i=0; i<types.length; i++ ) {
if ( ctype.cascade(i).doCascade(action) ) cascade(
session, children[i], types[i], action, cascadeTo
);
}
}
}
}
/**
* Cascade an action from the parent object to all its children
*/
public static void cascade(SessionImplementor session, ClassPersister persister, Object parent, Cascades.CascadingAction action, int cascadeTo) throws SQLException, HibernateException {
if ( persister.hasCascades() ) { // performance opt
if ( log.isTraceEnabled() ) log.trace( "processing cascades for: " + persister.getClassName() );
Type[] types = persister.getPropertyTypes();
Cascades.CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles();
for ( int i=0; i<types.length; i++) {
if ( cascadeStyles[i].doCascade(action) )
cascade( session, persister.getPropertyValue(parent,i), types[i], action, cascadeTo );
}
if ( log.isTraceEnabled() ) log.trace( "done processing cascades for: " + persister.getClassName() );
}
}
}