From: Michael D. <mik...@us...> - 2005-02-06 01:59:13
|
Update of /cvsroot/nhibernate/nhibernate/src/NHibernate/Impl In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv26787/Impl Modified Files: CollectionEntry.cs SessionImpl.cs Added Files: AbstractVisitor.cs CollectionKey.cs FlushVisitor.cs OnLockVisitor.cs OnUpdateVisitor.cs ProxyVisitor.cs ReattachVisitor.cs WrapVisitor.cs Log Message: sergey's patch for the visitor pattern from h2.1. Index: SessionImpl.cs =================================================================== RCS file: /cvsroot/nhibernate/nhibernate/src/NHibernate/Impl/SessionImpl.cs,v retrieving revision 1.63 retrieving revision 1.64 diff -C2 -d -r1.63 -r1.64 *** SessionImpl.cs 31 Jan 2005 03:25:39 -0000 1.63 --- SessionImpl.cs 6 Feb 2005 01:59:02 -0000 1.64 *************** *** 97,101 **** /// and an <see cref="CollectionEntry"/> as the value. /// </summary> ! private IdentityMap collections; /// <summary> --- 97,107 ---- /// and an <see cref="CollectionEntry"/> as the value. /// </summary> ! private IdentityMap collectionEntries; ! [...1666 lines suppressed...] + { + if ( log.IsDebugEnabled ) + log.Debug( "creating collection wrapper:" + MessageHelper.InfoString(persister, id) ); + collection = persister.CollectionType.Instantiate(this, persister); //TODO: suck into CollectionPersister.instantiate() + AddUninitializedCollection(collection, persister, id); + if ( persister.IsArray ) + { + InitializeCollection(collection, false); + AddArrayHolder( (ArrayHolder) collection ); + } + else if ( !persister.IsLazy ) + { + //nonlazyCollections.Add(collection); + } + return collection.GetValue(); + } + } } } \ No newline at end of file --- NEW FILE: ProxyVisitor.cs --- using System; using System.Collections; using NHibernate.Type; namespace NHibernate.Impl { internal abstract class ProxyVisitor : AbstractVisitor { public ProxyVisitor(SessionImpl session) : base(session) { } protected override object ProcessEntity(object value, EntityType entityType) { if (value != null) { Session.ReassociateIfUninitializedProxy(value); // if it is an initialized proxy, let cascade // handle it later on } return null; } } } --- NEW FILE: ReattachVisitor.cs --- using System; using System.Collections; using NHibernate.Type; namespace NHibernate.Impl { /// <summary> /// Abstract superclass of visitors that reattach collections /// </summary> internal abstract class ReattachVisitor : ProxyVisitor { private readonly object key; protected object Key { get { return key; } } public ReattachVisitor(SessionImpl session, object key) : base(session) { this.key = key; } protected override object ProcessComponent(object component, IAbstractComponentType componentType) { IType[] types = componentType.Subtypes; if (component == null) { ProcessValues(new object[types.Length], types); } else { base.ProcessComponent(component, componentType); } return null; } } } --- NEW FILE: AbstractVisitor.cs --- using System; using System.Collections; using NHibernate.Type; using NHibernate.Persister; namespace NHibernate.Impl { /// <summary> /// Abstract superclass of algorithms that walk a tree of property values /// of an entity, and perform specific functionality for collections, /// components and associated entities. /// </summary> internal abstract class AbstractVisitor { private readonly SessionImpl session; protected AbstractVisitor(SessionImpl session) { this.session = session; } /// <summary> /// Dispatch each property value to <see cref="ProcessValue" />. /// </summary> /// <param name="values"></param> /// <param name="types"></param> public virtual void ProcessValues(object[] values, IType[] types) { for (int i = 0; i < values.Length; i++) { ProcessValue(values[i], types[i]); } } protected virtual object ProcessComponent(object component, IAbstractComponentType componentType) { if (component != null) { ProcessValues(componentType.GetPropertyValues(component, session), componentType.Subtypes); } return null; } /// <summary> /// Visit a property value. Dispatch to the correct handler /// for the property type. /// </summary> /// <param name="value"></param> /// <param name="type"></param> /// <returns></returns> protected object ProcessValue(object value, IType type) { if (type.IsPersistentCollectionType) { // Even process null collections return ProcessCollection(value, (PersistentCollectionType) type); } else if (type.IsEntityType) { return ProcessEntity(value, (EntityType) type); } else if (type.IsComponentType) { //TODO: what about a null component with a collection! // we also need to clean up that "null collection" return ProcessComponent(value, (IAbstractComponentType) type); } else { return null; } } /// <summary> /// Walk the tree starting from the given entity. /// </summary> /// <param name="obj"></param> /// <param name="persister"></param> public virtual void Process(object obj, IClassPersister persister) { ProcessValues( persister.GetPropertyValues(obj), persister.PropertyTypes); } /// <summary> /// Visit a collection. Default superclass implementation is a no-op. /// </summary> /// <param name="collection"></param> /// <param name="type"></param> /// <returns></returns> protected virtual object ProcessCollection(object collection, PersistentCollectionType type) { return null; } /// <summary> /// Visit a many-to-one or one-to-one associated entity. Default /// superclass implementation is a no-op. /// </summary> /// <param name="value"></param> /// <param name="entityType"></param> /// <returns></returns> protected virtual object ProcessEntity(object value, EntityType entityType) { return null; } protected SessionImpl Session { get { return session; } } } } --- NEW FILE: CollectionKey.cs --- using System; using System.Collections; using NHibernate.Collection; namespace NHibernate.Impl { [Serializable] internal sealed class CollectionKey { private string role; private object key; public CollectionKey(string role, object key) { this.role = role; this.key = key; } public CollectionKey(CollectionPersister persister, object key) : this(persister.Role, key) { } public override bool Equals(object obj) { CollectionKey that = (CollectionKey) obj; return Equals (key, that.key) && Equals (role, that.role); } public override int GetHashCode() { int result = 17; result = 37 * result + key.GetHashCode(); result = 37 * result + role.GetHashCode(); return result; } } } --- NEW FILE: WrapVisitor.cs --- using System; using System.Collections; using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Type; using log4net; namespace NHibernate.Impl { internal class WrapVisitor : ProxyVisitor { private static readonly ILog log = LogManager.GetLogger(typeof(WrapVisitor)); private bool substitute = false; public bool IsSubstitutionRequired { get { return substitute; } } public WrapVisitor(SessionImpl session) : base (session) { } protected override object ProcessCollection(object collection, PersistentCollectionType collectionType) { if ( collection is PersistentCollection ) { PersistentCollection coll = (PersistentCollection) collection; if ( coll.SetCurrentSession(Session) ) { Session.ReattachCollection( coll, coll.CollectionSnapshot ); } return null; } else { return ProcessArrayOrNewCollection(collection, collectionType); } } private object ProcessArrayOrNewCollection(object collection, PersistentCollectionType collectionType) { if (collection == null) return null; CollectionPersister persister = Session.GetCollectionPersister( collectionType.Role ); if ( collectionType.IsArrayType ) { ArrayHolder ah = Session.GetArrayHolder(collection); if (ah == null) { ah = new ArrayHolder(Session, collection); Session.AddNewCollection(ah, persister); Session.AddArrayHolder(ah); } return null; } else { PersistentCollection persistentCollection = collectionType.Wrap(Session, collection); Session.AddNewCollection(persistentCollection, persister); if ( log.IsDebugEnabled ) log.Debug( "Wrapped collection in role: " + collectionType.Role ); return persistentCollection; //Force a substitution! } } public override void ProcessValues(object[] values, IType[] types) { for (int i = 0; i < types.Length; i++) { object result = ProcessValue( values[i], types[i] ); if ( result != null ) { substitute = true; values[i] = result; } } } protected override object ProcessComponent(object component, IAbstractComponentType componentType) { if (component == null) return null; object[] values = componentType.GetPropertyValues( component, Session ); IType[] types = componentType.Subtypes; bool substituteComponent = false; for ( int i=0; i<types.Length; i++ ) { object result = ProcessValue( values[i], types[i] ); if (result != null) { substituteComponent = true; values[i] = result; } } if (substituteComponent) { componentType.SetPropertyValues(component, values); } return null; } public override void Process(object obj, NHibernate.Persister.IClassPersister persister) { object[] values = persister.GetPropertyValues(obj); IType[] types = persister.PropertyTypes; ProcessValues(values, types); if ( IsSubstitutionRequired ) persister.SetPropertyValues(obj, values); } } } Index: CollectionEntry.cs =================================================================== RCS file: /cvsroot/nhibernate/nhibernate/src/NHibernate/Impl/CollectionEntry.cs,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** CollectionEntry.cs 23 Jan 2005 15:52:07 -0000 1.4 --- CollectionEntry.cs 6 Feb 2005 01:59:02 -0000 1.5 *************** *** 16,22 **** private static readonly ILog log = LogManager.GetLogger( typeof( CollectionEntry ) ); ! /// <summary></summary> ! // false by default ! internal bool dirty; /// <summary> --- 16,24 ---- private static readonly ILog log = LogManager.GetLogger( typeof( CollectionEntry ) ); ! /// <summary> ! /// Collections detect changes made via their public interface ! /// and mark themselves as dirty. False by default. ! /// </summary> ! private bool dirty; /// <summary> *************** *** 69,72 **** --- 71,81 ---- /// <summary> + /// If we instantiate a collection during the <see cref="ISession.Flush" /> + /// process, we must ignore it for the rest of the flush. + /// </summary> + [NonSerialized] + internal bool ignore; + + /// <summary> /// Indicates that the Collection has been fully initialized. /// </summary> *************** *** 98,102 **** internal object currentKey; ! /// <summary></summary> internal object loadedKey; --- 107,114 ---- internal object currentKey; ! /// <summary> ! /// The identifier of the Entity that is the owner of this Collection ! /// during the load or post flush. ! /// </summary> internal object loadedKey; *************** *** 116,122 **** public CollectionEntry() { ! // dirty is initialized to false by runtime //this.dirty = false; this.initialized = true; } --- 128,144 ---- public CollectionEntry() { ! // A newly wrapped collection is NOT dirty (or we get unnecessary version updates) //this.dirty = false; this.initialized = true; + + // New collections that get found and wrapped during flush shouldn't be ignored + //this.ignore = false; + } + + public CollectionEntry( CollectionPersister loadedPersister, object loadedID ) + // Detached collection wrappers that get found and reattached + // during flush shouldn't be ignored + : this( loadedPersister, loadedID, false ) + { } *************** *** 126,137 **** /// <param name="loadedPersister">The <see cref="CollectionPersister"/> that persists this Collection type.</param> /// <param name="loadedID">The identifier of the Entity that is the owner of this Collection.</param> ! /// <param name="initialized">A boolean indicating if the collection has been initialized.</param> ! public CollectionEntry( CollectionPersister loadedPersister, object loadedID, bool initialized ) { // dirty is initialized to false by runtime //this.dirty = false; ! this.initialized = initialized; this.loadedKey = loadedID; SetLoadedPersister( loadedPersister ); } --- 148,160 ---- /// <param name="loadedPersister">The <see cref="CollectionPersister"/> that persists this Collection type.</param> /// <param name="loadedID">The identifier of the Entity that is the owner of this Collection.</param> ! /// <param name="ignore">A boolean indicating whether to ignore the collection during current (or next) flush.</param> ! public CollectionEntry( CollectionPersister loadedPersister, object loadedID, bool ignore ) { // dirty is initialized to false by runtime //this.dirty = false; ! this.initialized = false; this.loadedKey = loadedID; SetLoadedPersister( loadedPersister ); + this.ignore = ignore; } *************** *** 151,156 **** this.snapshot = cs.Snapshot; this.loadedKey = cs.Key; - SetLoadedPersister( factory.GetCollectionPersister( cs.Role ) ); this.initialized = true; } --- 174,182 ---- this.snapshot = cs.Snapshot; this.loadedKey = cs.Key; this.initialized = true; + // Detached collections that get found and reattached during flush + // shouldn't be ignored + //this.ignore = false; + SetLoadedPersister( factory.GetCollectionPersister( cs.Role ) ); } *************** *** 226,252 **** public void PostFlush( PersistentCollection collection ) { ! // the CollectionEntry should be processed if we are in the PostFlush() ! if( !processed ) { ! throw new AssertionFailure( "Hibernate has a bug processing collections" ); } ! // now that the flush has gone through move everything that is the current ! // over to the loaded fields and set dirty to false since the db & collection ! // are in synch. ! loadedKey = currentKey; ! SetLoadedPersister( currentPersister ); ! dirty = false; ! // collection needs to know its' representation in memory and with ! // the db is now in synch - esp important for collections like a bag ! // that can add without initializing the collection. ! collection.PostFlush(); ! // if it was initialized or any of the scheduled actions were performed then ! // need to resnpashot the contents of the collection. ! if( initialized && ( doremove || dorecreate || doupdate ) ) ! { ! snapshot = collection.GetSnapshot( loadedPersister ); //re-snapshot } } --- 252,285 ---- public void PostFlush( PersistentCollection collection ) { ! if( ignore ) { ! ignore = false; } + else + { + // the CollectionEntry should be processed if we are in the PostFlush() + if( !processed ) + { + throw new AssertionFailure( "collection was not processed by Flush()" ); + } ! // now that the flush has gone through move everything that is the current ! // over to the loaded fields and set dirty to false since the db & collection ! // are in synch. ! loadedKey = currentKey; ! SetLoadedPersister( currentPersister ); ! dirty = false; ! // collection needs to know its' representation in memory and with ! // the db is now in synch - esp important for collections like a bag ! // that can add without initializing the collection. ! collection.PostFlush(); ! // if it was initialized or any of the scheduled actions were performed then ! // need to resnpashot the contents of the collection. ! if( initialized && ( doremove || dorecreate || doupdate ) ) ! { ! InitSnapshot(collection, loadedPersister); ! } } } *************** *** 254,257 **** --- 287,295 ---- #region Engine.ICollectionSnapshot Members + internal void InitSnapshot(PersistentCollection collection, CollectionPersister persister) + { + snapshot = collection.GetSnapshot( persister ); + } + /// <summary></summary> public object Key *************** *** 278,282 **** } - /// <summary></summary> public void SetDirty() { --- 316,319 ---- *************** *** 284,291 **** } ! /// <summary></summary> ! public bool IsInitialized { ! get { return initialized; } } --- 321,330 ---- } ! /// <summary> ! /// ! /// </summary> ! public bool WasDereferenced { ! get { return loadedKey == null; } } *************** *** 300,304 **** /// responsible for the Collection. /// </param> ! private void SetLoadedPersister( CollectionPersister persister ) { loadedPersister = persister; --- 339,343 ---- /// responsible for the Collection. /// </param> ! internal void SetLoadedPersister( CollectionPersister persister ) { loadedPersister = persister; --- NEW FILE: FlushVisitor.cs --- using System; using System.Collections; using NHibernate.Collection; using NHibernate.Type; namespace NHibernate.Impl { internal class FlushVisitor : AbstractVisitor { private object owner; public FlushVisitor( SessionImpl session, object owner ) : base( session ) { this.owner = owner; } protected override object ProcessCollection(object collection, PersistentCollectionType type) { if (collection!=null) { PersistentCollection coll; if ( type.IsArrayType ) { coll = Session.GetArrayHolder(collection); } else { coll = (PersistentCollection) collection; } Session.UpdateReachableCollection(coll, type, owner); } return null; } } } --- NEW FILE: OnUpdateVisitor.cs --- using System; using System.Collections; using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Type; namespace NHibernate.Impl { internal class OnUpdateVisitor : ReattachVisitor { public OnUpdateVisitor(SessionImpl session, object key) : base (session, key) { } protected override object ProcessCollection(object collection, PersistentCollectionType type) { CollectionPersister persister = Session.GetCollectionPersister( type.Role ); if ( collection is PersistentCollection ) { PersistentCollection wrapper = (PersistentCollection) collection; if ( wrapper.SetCurrentSession(Session) ) { //a "detached" collection! ICollectionSnapshot snapshot = wrapper.CollectionSnapshot; if ( !SessionImpl.IsOwnerUnchanged(snapshot, persister, Key) ) { // if the collection belonged to a different entity, // clean up the existing state of the collection Session.RemoveCollection(persister, Key); } Session.ReattachCollection(wrapper, snapshot); } else { // a collection loaded in the current session // can not possibly be the collection belonging // to the entity passed to update() Session.RemoveCollection(persister, Key); } } else { // null or brand new collection // this will also (inefficiently) handle arrays, which have // no snapshot, so we can't do any better Session.RemoveCollection(persister, Key); //processArrayOrNewCollection(collection, type); } return null; } } } --- NEW FILE: OnLockVisitor.cs --- using System; using System.Collections; using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Type; namespace NHibernate.Impl { /// <summary> /// When a transient entity is passed to <see cref="ISession.Lock" />, we must inspect all its collections and /// 1. associate any uninitialized PersistentCollections with this session /// 2. associate any initialized PersistentCollections with this session, using the /// existing snapshot /// 3. throw an exception for each "new" collection /// </summary> internal class OnLockVisitor : ReattachVisitor { public OnLockVisitor(SessionImpl session, object key) : base(session, key) { } protected override object ProcessCollection(object collection, PersistentCollectionType type) { CollectionPersister persister = Session.GetCollectionPersister(type.Role); if (collection == null) { // Do nothing } else if ( collection is PersistentCollection ) { PersistentCollection coll = (PersistentCollection) collection; if ( coll.SetCurrentSession(Session) ) { ICollectionSnapshot snapshot = coll.CollectionSnapshot; if (SessionImpl.IsOwnerUnchanged( snapshot, persister, this.Key )) { // a "detached" collection that originally belonged to the same entity if ( snapshot.Dirty ) { throw new HibernateException("reassociated object has dirty collection"); } Session.ReattachCollection(coll, snapshot); } else { // a "detached" collection that belonged to a different entity throw new HibernateException("reassociated object has dirty collection reference"); } } else { // a collection loaded in the current session // can not possibly be the collection belonging // to the entity passed to update() throw new HibernateException("reassociated object has dirty collection reference"); } } else { // brand new collection //TODO: or an array!! we can't lock objects with arrays now?? throw new HibernateException("reassociated object has dirty collection reference"); } return null; } } } |