Java Mailing List Archive

http://www.gg3721.com/

Home » the NHibernate development list »

[NHibernate-development] Contribution : VetoInterceptor.1 (Patch)

Will Shaver

2008-01-30

Replies: Find Java Web Hosting

Author LoginPost Reply
Here's the patch file for the aforementioned addition.
--

Attached: Patch File, VetoInterceptor1.patch
Patch Contents:

Updates to core NHibernate-2.0:
    AbstractSaveEventListener, DefaultDeleteEventListener,
DefaultFlushEntityEventListener,
    DefaultSaveOrUpdateEventListener, IInterceptor, EmptyInterceptor

Addition of Test Case: NHSpecificTest/InterceptVeto

Updates to Documentation: manipulating_data.xml //includes a rewrite
of the Interceptor section to include these additions, recommend
EmptyInterceptor instead of IInterceptor, and conversion of "boolean"
to "bool".

--

-Will Shaver
Index: doc/reference/modules/manipulating_data.xml
===================================================================
--- doc/reference/modules/manipulating_data.xml  (revision 3271)
+++ doc/reference/modules/manipulating_data.xml  (working copy)
@@(protected) @@
     <title>Interceptors</title>
     <para>
        The <literal>IInterceptor</literal> interface provides callbacks from the session to the
-        application allowing the application to inspect and / or manipulate properties of a
-        persistent object before it is saved, updated, deleted or loaded. One
-        possible use for this is to track auditing information. For example, the following
-        <literal>IInterceptor</literal> automatically sets the <literal>CreateTimestamp</literal>
+        application allowing the application to inspect, manipulate, and reject changes to properties of a
+        persistent object before it is saved, updated, deleted or loaded.
+    </para>
+    <para>
+      The <literal>EmptyInterceptor</literal> class implements <literal>IInterceptor</literal> with default
+      values, allowing developers to easily implement a subset of all available methods. Extending the
+      <literal>EmptyInterceptor</literal> class is also recommended to reduce code changes required by
+      future additions to the <literal>IInterceptor</literal> interface.
+    </para>
+    <para>        
+        One possible use for this is to track auditing information. For example, the following extension
+      of the <literal>EmptyInterceptor</literal> automatically sets the <literal>CreateTimestamp</literal>
        when an <literal>IAuditable</literal> is created and updates the
        <literal>LastUpdateTimestamp</literal> property when an <literal>IAuditable</literal> is
-        updated.
+        updated. It also performs a logical deletion by setting the <literal>DateDeletedTimestamp</literal>
+      property and returning <literal>DeleteVeto.VetoWithCascade</literal> when an <literal>IAuditable</literal>
+      is deleted. Returning <literal>DeleteVeto.VetoWithCascade</literal> allows additional deletions normally caused
+      by <literal>cascade="delete"</literal> in the mapping file to continue, thereby allowing NHibernate to
     </para>
-
+    
     <programlisting><![CDATA[using System;
using NHibernate.Type;
+using NHibernate;

namespace NHibernate.Test
{
  [Serializable]
-   public class AuditInterceptor : IInterceptor
+   public class AuditInterceptor : EmptyInterceptor
  {
 
-     private int updates;
-     private int creates;
+     private int creates, updates, deletes;
 
-     public void OnDelete(object entity,
-                   object id,
-                   object[] state,
-                   string[] propertyNames,
-                   IType[] types)
+     public override InterceptVeto OnBeforeDelete(object entity)
     {
-        // do nothing
+        if ( entity is IAuditable )
+        {
+           deletes++;
+           ((IAuditable) entity).DateDeletedTimestamp = DateTime.Now;
+           return InterceptVeto.VetoWithCascade;
+        }
+        return InterceptVeto.NoVeto;
     }
 
-     public boolean OnFlushDirty(object entity,
-                        object id,
-                        object[] currentState,
-                        object[] previousState,
-                        string[] propertyNames,
-                        IType[] types) {
-  
+     public override bool OnFlushDirty(object entity, object id,
+                            object[] currentState, object[] previousState,
+                            string[] propertyNames, IType[] types)
+     {
        if ( entity is IAuditable )
        {
          updates++;
@@(protected) @@
        return false;
     }
 
-     public boolean OnLoad(object entity,
-                    object id,
-                    object[] state,
-                    string[] propertyNames,
-                    IType[] types)
+     public override bool OnSave(object entity, object id, object[] state,
+                        string[] propertyNames, IType[] types)
     {
-        return false;
-     }
-  
-     public boolean OnSave(object entity,
-                    object id,
-                    object[] state,
-                    string[] propertyNames,
-                    IType[] types)
-     {
        if ( entity is IAuditable )
        {
          creates++;
@@(protected) @@
        return false;
     }
 
-     public void PostFlush(ICollection entities)
+     public override void PostFlush(ICollection entities)
     {
-        Console.Out.WriteLine("Creations: {0}, Updates: {1}", creates, updates);
+        Console.WriteLine("Creations: {0}, Updates: {1}, Deletes: {2}",
+                    creates, updates, deletes);
     }
 
-     public void PreFlush(ICollection entities) {
-        updates=0;
+     public override void PreFlush(ICollection entities)
+     {        
        creates=0;
+        updates=0;
+        deletes=0;
     }
-    
-     ......
-     ......
  }
}]]></programlisting>

Index: src/NHibernate.Test/NHibernate.Test-2.0.csproj
===================================================================
--- src/NHibernate.Test/NHibernate.Test-2.0.csproj  (revision 3271)
+++ src/NHibernate.Test/NHibernate.Test-2.0.csproj  (working copy)
@@(protected) @@
  <Compile Include="NHSpecificTest\CollectionFixture.cs" />
  <Compile Include="NHSpecificTest\HqlOnMapWithForumula\Domain.cs" />
  <Compile Include="NHSpecificTest\HqlOnMapWithForumula\Fixture.cs" />
+   <Compile Include="NHSpecificTest\InterceptVeto\Employee.cs" />
+   <Compile Include="NHSpecificTest\InterceptVeto\Employer.cs" />
+   <Compile Include="NHSpecificTest\InterceptVeto\Fixture.cs" />
  <Compile Include="NHSpecificTest\ListsWithHoles\Employee.cs" />
  <Compile Include="NHSpecificTest\ListsWithHoles\Fixture.cs" />
  <Compile Include="NHSpecificTest\LoadingNullEntityInSet\Employee.cs" />
@@(protected) @@
  <Service Include="{B4F97281-0DBD-4835-9ED8-7DFB966E87FF}" />
 </ItemGroup>
 <ItemGroup>
+   <EmbeddedResource Include="NHSpecificTest\InterceptVeto\Mappings.hbm.xml" />
+ </ItemGroup>
+ <ItemGroup>
  <Folder Include="Properties\" />
  <Folder Include="Unionsubclass2\" />
 </ItemGroup>
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs  (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs  (revision 0)
@@(protected) @@
+using System;
+using System.Collections;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+  public class Employer
+  {
+    private int id;
+    private string name;
+    private IList employees = new ArrayList();
+
+    public Employer()
+    {
+    }
+
+    public Employer(string name)
+    {
+      this.name = name;
+    }
+
+    public int Id
+    {
+      get { return id; }
+      set { id = value; }
+    }
+
+    public string Name
+    {
+      get { return name; }
+      set { name = value; }
+    }
+
+    public IList Employees
+    {
+      get { return employees; }
+      set { employees = value; }
+    }
+
+    public void AddEmployee(Employee employee)
+    {
+      employees.Add(employee);
+      employee.Employer = this;
+    }
+  }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml  (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml  (revision 0)
@@(protected) @@
+<?xml version="1.0" encoding="utf-8" ?>
+<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernate.Test.NHSpecificTest.InterceptVeto" assembly="NHibernate.Test" >
+  <class name="Employer" lazy="false">
+    <id name="Id">
+      <generator class="native" />
+    </id>
+    <property name="Name" />
+    <bag name="Employees" cascade="all" inverse="true">
+      <key column="EMR_ID" />
+      <one-to-many class="Employee"/>
+    </bag>
+  </class>
+
+  <class name="Employee" table="Employees" lazy="false">
+    <id name="Id">
+      <generator class="native" />
+    </id>
+    <property name="Name" />
+    <many-to-one name="Employer" column="EMR_ID" />
+  </class>
+</hibernate-mapping>
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs  (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs  (revision 0)
@@(protected) @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+  public class Employee
+  {
+    private int id;
+    private string name;
+    private Employer employer;
+
+    public Employee()
+    {
+    }
+
+    public Employee(string name)
+    {
+      this.name = name;
+    }
+
+    public int Id
+    {
+      get { return id; }
+      set { id = value; }
+    }
+
+    public string Name
+    {
+      get { return name; }
+      set { name = value; }
+    }
+
+    public Employer Employer
+    {
+      get { return employer; }
+      set { employer = value; }
+    }
+  }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs  (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs  (revision 0)
@@(protected) @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+  [TestFixture]
+  public class InterceptVetoFigure : BugTestCase
+  {
+
+    protected void StandardSetUp()
+    {
+      using(ISession session = OpenSession())
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          Employer emr = new Employer("Test Employer 1");
+          Employee[] employees = new Employee[2];
+          for(int i = 0; i < employees.Length; i++)
+          {
+            employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+            emr.AddEmployee(employees[i]);
+
+          }
+          session.Save(emr);
+          tx.Commit();
+        }
+      }
+    }
+    protected override void OnTearDown()
+    {
+      base.OnTearDown();
+      using(ISession session = OpenSession())
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          session.Delete("from Employee");
+          session.Delete("from Employer");
+          tx.Commit();
+        }
+      }
+    }
+
+    [Test]
+    public void NoVeto_ShouldInsertUpdateDeleteEverything()
+    {
+      StandardSetUp();
+      //note that the EmptyInterceptor returns NoVeto from all three OnBefore... methods
+      using(ISession session = OpenSession())
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Assert.AreEqual(1, employers.Count, "Persisting an employer failed.");
+          Employer emr = employers[0] as Employer;
+          Assert.AreEqual(2, emr.Employees.Count, "Persisting cascaded employees failed");
+          emr.Name = "Big Pizza";
+          ((Employee)emr.Employees[0]).Name = "Joe";
+          ((Employee)emr.Employees[1]).Name = "Fred";
+          tx.Commit();
+        }
+      }
+
+      using(ISession session = OpenSession())
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Employer emr = employers[0] as Employer;
+          Assert.AreEqual("Big Pizza", emr.Name, "Saving a change on employer failed.");
+          Employee emp0, emp1;
+          if(((Employee)emr.Employees[0]).Name == "Joe")
+          {
+            emp0 = (Employee)emr.Employees[0];
+            emp1 = (Employee)emr.Employees[1];
+          }
+          else // just in case the order changes, bag order not guaranteed
+          {
+            emp0 = (Employee)emr.Employees[1];
+            emp1 = (Employee)emr.Employees[0];
+          }
+          Assert.AreEqual("Joe", emp0.Name, "Saving cascaded employees failed");
+          Assert.AreEqual("Fred", emp1.Name, "Saving cascaded employees failed");
+          session.Delete(emr); //this is needed for next two asserts
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(0, employers.Count, "Deleting an employer failed.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(0, employees.Count, "Cascaded delete of employees failed.");
+      }
+    }
+
+    [Test]
+    public void OnBeforeDelete_VetoNoCascade_ShouldDeleteNothing()
+    {
+      StandardSetUp();
+      VetoNoCascadeInterceptor interceptor = new VetoNoCascadeInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Employer emp = employers[0] as Employer;
+          interceptor.entityToCheck = emp;
+          session.Delete(emp);
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(1, employers.Count, "VetoNoCascade failed to cancel a delete of an employer.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(2, employees.Count, "VetoNoCascade failed to cancel a delete of an employee");
+      }
+    }
+
+    [Test]
+    public void OnBeforeDelete_VetoWithCascade_ShouldDeleteChildren()
+    {
+      StandardSetUp();
+      VetoWithCascadeInterceptor interceptor = new VetoWithCascadeInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Employer emp = employers[0] as Employer;
+          interceptor.entityToCheck = emp;
+          session.Delete(emp);
+          emp.Employees.Clear();
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(1, employers.Count, "VetoWithCascade failed to cancel a delete of an employer.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(0, employees.Count, "VetoWithCascade failed to delete all employees");
+      }
+    }
+
+    [Test]
+    public void OnBeforeInsert_VetoNoCascade_ShouldSaveNothing()
+    {
+      VetoNoCascadeInterceptor interceptor = new VetoNoCascadeInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          Employer emr = new Employer("Test Employer 1");
+          Employee[] employees = new Employee[2];
+          for(int i = 0; i < employees.Length; i++)
+          {
+            employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+            emr.AddEmployee(employees[i]);
+          }
+          interceptor.entityToCheck = emr;
+          session.Save(emr);
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(0, employers.Count, "VetoNoCascade saved an employer.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(0, employees.Count, "VetoNoCascade saved an employee");
+      }
+    }
+
+    [Test]
+    public void OnBeforeInsert_VetoWithCascade_ShouldSaveChildren()
+    {
+      VetoWithCascadeInterceptor interceptor = new VetoWithCascadeInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          Employer emr = new Employer("Test Employer 1");
+          Employee[] employees = new Employee[2];
+          for(int i = 0; i < employees.Length; i++)
+          {
+            employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+            emr.Employees.Add(employees[i]);
+            //emr.AddEmployee(employees[i]); // can't do this or foreign keys will fail, this only
+                             //works when the foreign key field can be null.
+          }
+          interceptor.entityToCheck = emr;
+          session.Save(emr);
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(0, employers.Count, "VetoWithCascade saved an employer.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(2, employees.Count, "VetoNoCascade didn't save the employees");
+      }
+    }
+
+    [Test]
+    public void OnBeforeUpdate_Veto_ShouldNotUpdate()
+    {
+      StandardSetUp();
+      UpdateVetoInterceptor interceptor = new UpdateVetoInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Employer emr = employers[0] as Employer;
+          emr.Name = "Big Pizza";
+          ((Employee)emr.Employees[0]).Name = "Joe";
+          ((Employee)emr.Employees[1]).Name = "Fred";
+          tx.Commit();
+        }
+      }
+
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Employer emr = employers[0] as Employer;
+        Assert.AreNotEqual("Big Pizza", emr.Name, "Vetoed change was saved on an Employer.");
+        Employee emp0, emp1;
+        if(((Employee)emr.Employees[0]).Name == "Joe")
+        {
+          emp0 = (Employee)emr.Employees[0];
+          emp1 = (Employee)emr.Employees[1];
+        }
+        else // just in case the order changes, bag order not guaranteed
+        {
+          emp0 = (Employee)emr.Employees[1];
+          emp1 = (Employee)emr.Employees[0];
+        }
+        Assert.AreNotEqual("Joe", emp0.Name, "Vetoed change was saved on an Employeee.");
+        Assert.AreNotEqual("Fred", emp1.Name, "Vetoed change was saved on an Employeee.");
+      }
+    }
+
+  }
+
+  public class VetoNoCascadeInterceptor : EmptyInterceptor
+  {
+    public object entityToCheck;
+
+    public override NHibernate.DeleteVeto OnBeforeDelete(object entity)
+    {
+      if(entity == entityToCheck) { return NHibernate.DeleteVeto.VetoNoCascade; }
+      return NHibernate.DeleteVeto.NoVeto;
+    }
+
+    public override NHibernate.InsertVeto OnBeforeInsert(object entity)
+    {
+      if(entity == entityToCheck) { return NHibernate.InsertVeto.VetoNoCascade; }
+      return NHibernate.InsertVeto.NoVeto;
+    }
+  }
+  public class VetoWithCascadeInterceptor : EmptyInterceptor
+  {
+    public object entityToCheck;
+
+    public override NHibernate.DeleteVeto OnBeforeDelete(object entity)
+    {
+      if(entity == entityToCheck) { return NHibernate.DeleteVeto.VetoWithCascade; }
+      return NHibernate.DeleteVeto.NoVeto;
+    }
+
+    public override NHibernate.InsertVeto OnBeforeInsert(object entity)
+    {
+      if(entity == entityToCheck) { return NHibernate.InsertVeto.VetoWithCascade; }
+      return NHibernate.InsertVeto.NoVeto;
+    }
+  }
+  public class UpdateVetoInterceptor : EmptyInterceptor
+  {
+    public override NHibernate.UpdateVeto OnBeforeUpdate(object entity)
+    {
+      return NHibernate.UpdateVeto.Veto;
+    }
+  }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs  (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs  (revision 0)
@@(protected) @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+  public class Employee
+  {
+    private int id;
+    private string name;
+    private Employer employer;
+
+    public Employee()
+    {
+    }
+
+    public Employee(string name)
+    {
+      this.name = name;
+    }
+
+    public int Id
+    {
+      get { return id; }
+      set { id = value; }
+    }
+
+    public string Name
+    {
+      get { return name; }
+      set { name = value; }
+    }
+
+    public Employer Employer
+    {
+      get { return employer; }
+      set { employer = value; }
+    }
+  }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs  (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs  (revision 0)
@@(protected) @@
+using System;
+using System.Collections;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+  public class Employer
+  {
+    private int id;
+    private string name;
+    private IList employees = new ArrayList();
+
+    public Employer()
+    {
+    }
+
+    public Employer(string name)
+    {
+      this.name = name;
+    }
+
+    public int Id
+    {
+      get { return id; }
+      set { id = value; }
+    }
+
+    public string Name
+    {
+      get { return name; }
+      set { name = value; }
+    }
+
+    public IList Employees
+    {
+      get { return employees; }
+      set { employees = value; }
+    }
+
+    public void AddEmployee(Employee employee)
+    {
+      employees.Add(employee);
+      employee.Employer = this;
+    }
+  }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs  (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs  (revision 0)
@@(protected) @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+  [TestFixture]
+  public class InterceptVetoFigure : BugTestCase
+  {
+
+    protected void StandardSetUp()
+    {
+      using(ISession session = OpenSession())
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          Employer emr = new Employer("Test Employer 1");
+          Employee[] employees = new Employee[2];
+          for(int i = 0; i < employees.Length; i++)
+          {
+            employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+            emr.AddEmployee(employees[i]);
+
+          }
+          session.Save(emr);
+          tx.Commit();
+        }
+      }
+    }
+    protected override void OnTearDown()
+    {
+      base.OnTearDown();
+      using(ISession session = OpenSession())
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          session.Delete("from Employee");
+          session.Delete("from Employer");
+          tx.Commit();
+        }
+      }
+    }
+
+    [Test]
+    public void NoVeto_ShouldInsertUpdateDeleteEverything()
+    {
+      StandardSetUp();
+      //note that the EmptyInterceptor returns NoVeto from all three OnBefore... methods
+      using(ISession session = OpenSession())
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Assert.AreEqual(1, employers.Count, "Persisting an employer failed.");
+          Employer emr = employers[0] as Employer;
+          Assert.AreEqual(2, emr.Employees.Count, "Persisting cascaded employees failed");
+          emr.Name = "Big Pizza";
+          ((Employee)emr.Employees[0]).Name = "Joe";
+          ((Employee)emr.Employees[1]).Name = "Fred";
+          tx.Commit();
+        }
+      }
+
+      using(ISession session = OpenSession())
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Employer emr = employers[0] as Employer;
+          Assert.AreEqual("Big Pizza", emr.Name, "Saving a change on employer failed.");
+          Employee emp0, emp1;
+          if(((Employee)emr.Employees[0]).Name == "Joe")
+          {
+            emp0 = (Employee)emr.Employees[0];
+            emp1 = (Employee)emr.Employees[1];
+          }
+          else // just in case the order changes, bag order not guaranteed
+          {
+            emp0 = (Employee)emr.Employees[1];
+            emp1 = (Employee)emr.Employees[0];
+          }
+          Assert.AreEqual("Joe", emp0.Name, "Saving cascaded employees failed");
+          Assert.AreEqual("Fred", emp1.Name, "Saving cascaded employees failed");
+          session.Delete(emr); //this is needed for next two asserts
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(0, employers.Count, "Deleting an employer failed.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(0, employees.Count, "Cascaded delete of employees failed.");
+      }
+    }
+
+    [Test]
+    public void OnBeforeDelete_VetoNoCascade_ShouldDeleteNothing()
+    {
+      StandardSetUp();
+      VetoNoCascadeInterceptor interceptor = new VetoNoCascadeInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Employer emp = employers[0] as Employer;
+          interceptor.entityToCheck = emp;
+          session.Delete(emp);
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(1, employers.Count, "VetoNoCascade failed to cancel a delete of an employer.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(2, employees.Count, "VetoNoCascade failed to cancel a delete of an employee");
+      }
+    }
+
+    [Test]
+    public void OnBeforeDelete_VetoWithCascade_ShouldDeleteChildren()
+    {
+      StandardSetUp();
+      VetoWithCascadeInterceptor interceptor = new VetoWithCascadeInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Employer emp = employers[0] as Employer;
+          interceptor.entityToCheck = emp;
+          session.Delete(emp);
+          emp.Employees.Clear();
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(1, employers.Count, "VetoWithCascade failed to cancel a delete of an employer.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(0, employees.Count, "VetoWithCascade failed to delete all employees");
+      }
+    }
+
+    [Test]
+    public void OnBeforeInsert_VetoNoCascade_ShouldSaveNothing()
+    {
+      VetoNoCascadeInterceptor interceptor = new VetoNoCascadeInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          Employer emr = new Employer("Test Employer 1");
+          Employee[] employees = new Employee[2];
+          for(int i = 0; i < employees.Length; i++)
+          {
+            employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+            emr.AddEmployee(employees[i]);
+          }
+          interceptor.entityToCheck = emr;
+          session.Save(emr);
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(0, employers.Count, "VetoNoCascade saved an employer.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(0, employees.Count, "VetoNoCascade saved an employee");
+      }
+    }
+
+    [Test]
+    public void OnBeforeInsert_VetoWithCascade_ShouldSaveChildren()
+    {
+      VetoWithCascadeInterceptor interceptor = new VetoWithCascadeInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          Employer emr = new Employer("Test Employer 1");
+          Employee[] employees = new Employee[2];
+          for(int i = 0; i < employees.Length; i++)
+          {
+            employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+            emr.Employees.Add(employees[i]);
+            //emr.AddEmployee(employees[i]); // can't do this or foreign keys will fail, this only
+                             //works when the foreign key field can be null.
+          }
+          interceptor.entityToCheck = emr;
+          session.Save(emr);
+          tx.Commit();
+        }
+      }
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Assert.AreEqual(0, employers.Count, "VetoWithCascade saved an employer.");
+        IList employees = session.CreateQuery("select e from Employee as e").List();
+        Assert.AreEqual(2, employees.Count, "VetoNoCascade didn't save the employees");
+      }
+    }
+
+    [Test]
+    public void OnBeforeUpdate_Veto_ShouldNotUpdate()
+    {
+      StandardSetUp();
+      UpdateVetoInterceptor interceptor = new UpdateVetoInterceptor();
+      using(ISession session = sessions.OpenSession(interceptor))
+      {
+        using(ITransaction tx = session.BeginTransaction())
+        {
+          IList employers = session.CreateQuery("select e from Employer as e").List();
+          Employer emr = employers[0] as Employer;
+          emr.Name = "Big Pizza";
+          ((Employee)emr.Employees[0]).Name = "Joe";
+          ((Employee)emr.Employees[1]).Name = "Fred";
+          tx.Commit();
+        }
+      }
+
+      using(ISession session = OpenSession())
+      {
+        IList employers = session.CreateQuery("select e from Employer as e").List();
+        Employer emr = employers[0] as Employer;
+        Assert.AreNotEqual("Big Pizza", emr.Name, "Vetoed change was saved on an Employer.");
+        Employee emp0, emp1;
+        if(((Employee)emr.Employees[0]).Name == "Joe")
+        {
+          emp0 = (Employee)emr.Employees[0];
+          emp1 = (Employee)emr.Employees[1];
+        }
+        else // just in case the order changes, bag order not guaranteed
+        {
+          emp0 = (Employee)emr.Employees[1];
+          emp1 = (Employee)emr.Employees[0];
+        }
+        Assert.AreNotEqual("Joe", emp0.Name, "Vetoed change was saved on an Employeee.");
+        Assert.AreNotEqual("Fred", emp1.Name, "Vetoed change was saved on an Employeee.");
+      }
+    }
+
+  }
+
+  public class VetoNoCascadeInterceptor : EmptyInterceptor
+  {
+    public object entityToCheck;
+
+    public override NHibernate.DeleteVeto OnBeforeDelete(object entity)
+    {
+      if(entity == entityToCheck) { return NHibernate.DeleteVeto.VetoNoCascade; }
+      return NHibernate.DeleteVeto.NoVeto;
+    }
+
+    public override NHibernate.InsertVeto OnBeforeInsert(object entity)
+    {
+      if(entity == entityToCheck) { return NHibernate.InsertVeto.VetoNoCascade; }
+      return NHibernate.InsertVeto.NoVeto;
+    }
+  }
+  public class VetoWithCascadeInterceptor : EmptyInterceptor
+  {
+    public object entityToCheck;
+
+    public override NHibernate.DeleteVeto OnBeforeDelete(object entity)
+    {
+      if(entity == entityToCheck) { return NHibernate.DeleteVeto.VetoWithCascade; }
+      return NHibernate.DeleteVeto.NoVeto;
+    }
+
+    public override NHibernate.InsertVeto OnBeforeInsert(object entity)
+    {
+      if(entity == entityToCheck) { return NHibernate.InsertVeto.VetoWithCascade; }
+      return NHibernate.InsertVeto.NoVeto;
+    }
+  }
+  public class UpdateVetoInterceptor : EmptyInterceptor
+  {
+    public override NHibernate.UpdateVeto OnBeforeUpdate(object entity)
+    {
+      return NHibernate.UpdateVeto.Veto;
+    }
+  }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml  (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml  (revision 0)
@@(protected) @@
+<?xml version="1.0" encoding="utf-8" ?>
+<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernate.Test.NHSpecificTest.InterceptVeto" assembly="NHibernate.Test" >
+  <class name="Employer" lazy="false">
+    <id name="Id">
+      <generator class="native" />
+    </id>
+    <property name="Name" />
+    <bag name="Employees" cascade="all" inverse="true">
+      <key column="EMR_ID" />
+      <one-to-many class="Employee"/>
+    </bag>
+  </class>
+
+  <class name="Employee" table="Employees" lazy="false">
+    <id name="Id">
+      <generator class="native" />
+    </id>
+    <property name="Name" />
+    <many-to-one name="Employer" column="EMR_ID" />
+  </class>
+</hibernate-mapping>
Index: src/NHibernate/EmptyInterceptor.cs
===================================================================
--- src/NHibernate/EmptyInterceptor.cs  (revision 3271)
+++ src/NHibernate/EmptyInterceptor.cs  (working copy)
@@(protected) @@
   {
     return sql;
   }
+
+    public virtual DeleteVeto OnBeforeDelete(object entity)
+    {
+      return DeleteVeto.NoVeto;
+    }
+
+    public virtual InsertVeto OnBeforeInsert(object entity)
+    {
+      return InsertVeto.NoVeto;
+    }
+
+    public virtual UpdateVeto OnBeforeUpdate(object entity)
+    {
+      return UpdateVeto.NoVeto;
+    }
 }
}
\ No newline at end of file
Index: src/NHibernate/Event/Default/AbstractSaveEventListener.cs
===================================================================
--- src/NHibernate/Event/Default/AbstractSaveEventListener.cs  (revision 3271)
+++ src/NHibernate/Event/Default/AbstractSaveEventListener.cs  (working copy)
@@(protected) @@
     //bool shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess;
     bool shouldDelayIdentityInserts = false;

+      InsertVeto veto = source.Interceptor.OnBeforeInsert(entity);
+      if(veto == InsertVeto.VetoNoCascade)
+      {
+        if(log.IsDebugEnabled)
+        {
+          log.Debug("insert veto" + MessageHelper.InfoString(persister, id, source.Factory));
+        }
+        return id;
+      }
+
+      if(veto == InsertVeto.VetoWithCascade)
+      {
+        if(log.IsDebugEnabled)
+        {
+          log.Debug("insert veto with cascade" + MessageHelper.InfoString(persister, id, source.Factory));
+        }
+        CascadeBeforeSave(source, persister, entity, anything);
+        CascadeAfterSave(source, persister, entity, anything);
+        return id;
+      }
+
     // Put a placeholder in entries, so we don't recurse back and try to save() the
     // same object again. QUESTION: should this be done before onSave() is called?
     // likewise, should it be done before onUpdate()?
Index: src/NHibernate/Event/Default/DefaultDeleteEventListener.cs
===================================================================
--- src/NHibernate/Event/Default/DefaultDeleteEventListener.cs  (revision 3271)
+++ src/NHibernate/Event/Default/DefaultDeleteEventListener.cs  (working copy)
@@(protected) @@
     object[] deletedState = CreateDeletedState(persister, currentState, session);
     entityEntry.DeletedState = deletedState;

-      session.Interceptor.OnDelete(entity, entityEntry.Id, deletedState, persister.PropertyNames, propTypes);
-
-      // before any callbacks, etc, so subdeletions see that this deletion happened first
-      persistenceContext.SetEntryStatus(entityEntry, Status.Deleted);
+      DeleteVeto veto = session.Interceptor.OnBeforeDelete(entity);
+        switch(veto)
+        {
+        case DeleteVeto.NoVeto:
+             {
+                session.Interceptor.OnDelete(entity, entityEntry.Id, deletedState, persister.PropertyNames, propTypes);
+            
+            // before any callbacks, etc, so subdeletions see that this deletion happened first
+            persistenceContext.SetEntryStatus(entityEntry, Status.Deleted);
+            break;
+             }
+        case DeleteVeto.VetoNoCascade:
+          {
+            if (log.IsDebugEnabled)
+            {
+              log.Debug("delete veto" + MessageHelper.InfoString(persister, entityEntry.Id, session.Factory));
+            }
+            return;
+          }
+        case DeleteVeto.VetoWithCascade:
+             {
+                if (log.IsDebugEnabled)
+                {
+                   log.Debug("delete veto with cascade" + MessageHelper.InfoString(persister, entityEntry.Id, session.Factory));
+                }
+                break;
+             }
+      }
+      
     EntityKey key = new EntityKey(entityEntry.Id, persister, session.EntityMode);

     CascadeBeforeDelete(session, persister, entity, entityEntry, transientEntities);
@@(protected) @@
     new Nullability(session).CheckNullability(entityEntry.DeletedState, persister, true);
     persistenceContext.NullifiableEntityKeys.Add(key);

-      // Ensures that containing deletions happen before sub-deletions
-      session.ActionQueue.AddAction(new EntityDeleteAction(entityEntry.Id, deletedState, version, entity, persister, isCascadeDeleteEnabled, session));
+      if (veto == DeleteVeto.NoVeto)
+      {
+        // Ensures that containing deletions happen before sub-deletions
+        session.ActionQueue.AddAction(new EntityDeleteAction(entityEntry.Id, deletedState, version, entity, persister, isCascadeDeleteEnabled, session));
+      }

     CascadeAfterDelete(session, persister, entity, transientEntities);

Index: src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs
===================================================================
--- src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs  (revision 3271)
+++ src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs  (working copy)
@@(protected) @@
     //TODO: avoid this for non-new instances where mightBeDirty==false
     bool substitute = WrapCollections(session, persister, types, values);

+      if (session.Interceptor.OnBeforeUpdate(entity) == UpdateVeto.Veto)
+      {
+        if (log.IsDebugEnabled)
+        {
+          log.Debug("Vetoed an update of an entity: " + MessageHelper.InfoString(persister, entry.Id, session.Factory));
+        }
+        return;
+      }
+
     if (IsUpdateNecessary(@(protected)))
     {
       substitute = ScheduleUpdate(@(protected);
Index: src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs
===================================================================
--- src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs  (revision 3271)
+++ src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs  (working copy)
@@(protected) @@
     }
   }

+    /// <summary> Performs an update of a DETACHED entity. </summary>
+    /// <param name="event">The update event to be handled. </param>
+    /// <param name="entity">The entity. </param>
+    /// <param name="persister">The entity persister </param>
   protected internal void PerformUpdate(SaveOrUpdateEvent @event, object entity, IEntityPersister persister)
   {
     if (!persister.IsMutable)
Index: src/NHibernate/IInterceptor.cs
===================================================================
--- src/NHibernate/IInterceptor.cs  (revision 3271)
+++ src/NHibernate/IInterceptor.cs  (working copy)
@@(protected) @@
   bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types);

   /// <summary>
-    /// Called before an object is deleted
+    /// Called as an object is being deleted, after OnBeforeDelete has approved the decision by returning NoVeto.
   /// </summary>
   /// <param name="entity"></param>
   /// <param name="id"></param>
+     /// <param name="state"></param>
   /// <param name="propertyNames"></param>
-    /// <param name="state"></param>
   /// <param name="types"></param>
   /// <remarks>
   /// It is not recommended that the interceptor modify the <c>state</c>.
   /// </remarks>
   void OnDelete(object entity, object id, object[] state, string[] propertyNames, IType[] types);
+
   /// <summary> Called before a collection is (re)created.</summary>
   void OnCollectionRecreate(object collection, object key);
   /// <summary> Called before a collection is deleted.</summary>
@@(protected) @@
   /// <param name="sql">sql to be prepared </param>
   /// <returns> original or modified sql </returns>
   SqlString OnPrepareStatement(SqlString sql);
+
+    /// <summary>
+    /// Called before the object is flushed to the database, allowing the flush to be cancelled.
+    /// </summary>
+    /// <param name="entity">The entity to be inserted.</param>
+    /// <remarks>
+    /// This function allows the update to be cancelled. A veto (with cascade or without cascade) will cause the entity not to be persisted.
+    /// It is possible to cancel inserts that make needed changes to foreign key associations thereby causing exceptions.
+    /// </remarks>
+    /// <returns>InterceptVeto</returns>
+    InsertVeto OnBeforeInsert(object entity);
+
+    /// <summary>
+    /// Called before an object being saved, allowing the save to be cancelled.
+    /// </summary>
+    /// <param name="entity">The entity to be saved.</param>
+    /// <remarks>
+    /// This function allows the insert to be cancelled. A veto (with cascade or without cascade)
+    /// will keep the original version of the entity in the database.
+    /// Return InterceptVeto.VetoWithCascade to continue updating other objects but not this one.
+    /// It is possible to cancel updates that make needed changes to foreign key associations thereby causing exceptions.
+    /// </remarks>
+    /// <returns>InterceptVeto</returns>
+    UpdateVeto OnBeforeUpdate(object entity);
+
+    /// <summary>
+    /// Called before an object is deleted, allowing the delete to be cancelled.
+    /// </summary>
+    /// <param name="entity">The entity to be deleted.</param>
+    /// <remarks>
+    /// This function allows the delete to be cancelled. Modifications may be made to the object state.
+    /// Perform logical deletion by modifying the entity and saving it under the current session.
+    /// Return InterceptVeto.VetoWithCascade to continue delete other objects following a logical delete.
+    /// (Note that saving it will cause the OnSave event to fire, which will happen after other physical deletions in the cascade.)
+    /// OnBeforeDelete will fire after the OnDelete (deprecated) ILifecycle event.
+    /// </remarks>
+    /// <returns>InterceptVeto</returns>
+    DeleteVeto OnBeforeDelete(object entity);
 }
+
+
+  public enum DeleteVeto
+  {
+    /// <summary>
+    /// Veto the delete with no further cascades
+    /// </summary>
+    VetoNoCascade,
+    /// <summary>
+    /// Veto the delete but cascade as though the delete had occured
+    /// </summary>
+    VetoWithCascade,
+    /// <summary>
+    /// Accept the delete
+    /// </summary>
+    NoVeto
+  }
+
+  public enum UpdateVeto
+  {
+    /// <summary>
+    /// Veto the update
+    /// </summary>
+    Veto,
+    /// <summary>
+    /// Accept the update
+    /// </summary>
+    NoVeto
+  }
+
+  public enum InsertVeto
+  {
+    /// <summary>
+    /// Veto the insert with no further cascades
+    /// </summary>
+    VetoNoCascade,
+    /// <summary>
+    /// Veto the insert but cascade as though the insert had occured
+    /// </summary>
+    VetoWithCascade,
+    /// <summary>
+    /// Accept the insert
+    /// </summary>
+    NoVeto
+  }
}
\ No newline at end of file
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Nhibernate-development mailing list
Nhibernate-development@(protected)
https://lists.sourceforge.net/lists/listinfo/nhibernate-development
©2008 gg3721.com - Jax Systems, LLC, U.S.A.