cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aadamc...@apache.org
Subject cayenne git commit: CAY-2030 Capturing a stream of commit changes
Date Fri, 23 Oct 2015 12:16:04 GMT
Repository: cayenne
Updated Branches:
  refs/heads/master b746d0671 -> f03867873


CAY-2030 Capturing a stream of commit changes

* removing hardcoding of to-many ignores
* adding flags to ignore all attributes, to-ones, to-manys


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/f0386787
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/f0386787
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/f0386787

Branch: refs/heads/master
Commit: f038678734bb68164830bc7090e467c8f99c579b
Parents: b746d06
Author: aadamchik <aadamchik@apache.org>
Authored: Fri Oct 23 14:10:28 2015 +0300
Committer: aadamchik <aadamchik@apache.org>
Committed: Fri Oct 23 15:12:35 2015 +0300

----------------------------------------------------------------------
 .../cayenne/lifecycle/audit/Auditable.java      |  43 +++++-
 .../meta/AuditablePostCommitEntityFactory.java  |   9 +-
 .../meta/DefaultPostCommitEntity.java           |  78 ----------
 .../meta/MutablePostCommitEntity.java           | 112 ++++++++++++++
 .../apache/cayenne/lifecycle/db/Auditable3.java |  11 ++
 .../apache/cayenne/lifecycle/db/Auditable4.java |  11 ++
 .../cayenne/lifecycle/db/auto/_Auditable3.java  |  51 +++++++
 .../cayenne/lifecycle/db/auto/_Auditable4.java  |  46 ++++++
 .../postcommit/PostCommitFilter_FilteredIT.java | 152 ++++++++++++++++++-
 .../lifecycle/unit/AuditableServerCase.java     |   9 ++
 .../src/test/resources/lifecycle-map.map.xml    |  27 ++++
 11 files changed, 461 insertions(+), 88 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/audit/Auditable.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/audit/Auditable.java
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/audit/Auditable.java
index 68a2732..a826bd8 100644
--- a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/audit/Auditable.java
+++ b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/audit/Auditable.java
@@ -25,6 +25,8 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+import org.apache.cayenne.lifecycle.postcommit.Confidential;
+
 /**
  * An annotation that adds auditing behavior to DataObjects.
  * 
@@ -36,10 +38,39 @@ import java.lang.annotation.Target;
 @Inherited
 public @interface Auditable {
 
-    String[] ignoredProperties() default {};
-    
-    /**
-     * @since 4.0
-     */
-    String[] confidential() default {};
+	/**
+	 * Returns an array of entity properties that should be excluded from audit.
+	 */
+	String[] ignoredProperties() default {};
+
+	/**
+	 * Returns whether all attributes should be excluded from audit.
+	 * 
+	 * @since 4.0
+	 */
+	boolean ignoreAttributes() default false;
+
+	/**
+	 * Returns whether all to-one relationships should be excluded from audit.
+	 * 
+	 * @since 4.0
+	 */
+	boolean ignoreToOneRelationships() default false;
+
+	/**
+	 * Returns whether all to-many relationships should be excluded from audit.
+	 * 
+	 * @since 4.0
+	 */
+	boolean ignoreToManyRelationships() default false;
+
+	/**
+	 * Returns an array of properties that should be treated as confidential.
+	 * I.e. their change should be recorded, but their values should be hidden
+	 * from listeners. In practice both old and new values will be set to an
+	 * instance of {@link Confidential}.
+	 * 
+	 * @since 4.0
+	 */
+	String[] confidential() default {};
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/AuditablePostCommitEntityFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/AuditablePostCommitEntityFactory.java
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/AuditablePostCommitEntityFactory.java
index f7ce2fd..424c7d2 100644
--- a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/AuditablePostCommitEntityFactory.java
+++ b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/AuditablePostCommitEntityFactory.java
@@ -89,13 +89,16 @@ public class AuditablePostCommitEntityFactory implements PostCommitEntityFactory
 		EntityResolver entityResolver = getEntityResolver();
 		ClassDescriptor classDescriptor = entityResolver.getClassDescriptor(entityName);
 
-		Auditable annotation = classDescriptor.getObjectClass().getAnnotation(Auditable.class);
-		if (annotation == null) {
+		Auditable a = classDescriptor.getObjectClass().getAnnotation(Auditable.class);
+		if (a == null) {
 			return BLOCKED_ENTITY;
 		}
 
 		ObjEntity entity = entityResolver.getObjEntity(entityName);
-		return new DefaultPostCommitEntity(entity, annotation.ignoredProperties(), annotation.confidential());
+		return new MutablePostCommitEntity(entity).setConfidential(a.confidential())
+				.setIgnoreProperties(a.ignoredProperties()).setIgnoreAttributes(a.ignoreAttributes())
+				.setIgnoreToOneRelationships(a.ignoreToOneRelationships())
+				.setIgnoreToManyRelationships(a.ignoreToManyRelationships());
 	}
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/DefaultPostCommitEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/DefaultPostCommitEntity.java
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/DefaultPostCommitEntity.java
deleted file mode 100644
index 7d6ad8c..0000000
--- a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/DefaultPostCommitEntity.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.lifecycle.postcommit.meta;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-
-/**
- * @since 4.0
- */
-public class DefaultPostCommitEntity implements PostCommitEntity {
-	
-	private Collection<String> ignoredProperties;
-	private Collection<String> confidentialProperties;
-
-	public DefaultPostCommitEntity(ObjEntity entity, String[] ignoredProperties, String[] confidentialProperties)
{
-
-		this.ignoredProperties = new HashSet<>();
-		this.confidentialProperties = new HashSet<>();
-
-		// ignoring to-many (presumably traced via changes to target entities)
-		// TODO: M:N relationships will not be tracked as a result...
-
-		for (ObjRelationship relationship : entity.getRelationships()) {
-			if (relationship.isToMany()) {
-				this.ignoredProperties.add(relationship.getName());
-			}
-		}
-
-		// ignore explicitly specified properties
-		if (ignoredProperties != null) {
-			for (String property : ignoredProperties) {
-				this.ignoredProperties.add(property);
-			}
-		}
-
-		if (confidentialProperties != null) {
-			for (String property : confidentialProperties) {
-				this.confidentialProperties.add(property);
-			}
-		}
-	}
-
-	@Override
-	public boolean isIncluded(String property) {
-		return !ignoredProperties.contains(property);
-	}
-
-	@Override
-	public boolean isIncluded() {
-		return true;
-	}
-
-	@Override
-	public boolean isConfidential(String property) {
-		return confidentialProperties.contains(property);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/MutablePostCommitEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/MutablePostCommitEntity.java
b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/MutablePostCommitEntity.java
new file mode 100644
index 0000000..3e61c40
--- /dev/null
+++ b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/postcommit/meta/MutablePostCommitEntity.java
@@ -0,0 +1,112 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.lifecycle.postcommit.meta;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+
+/**
+ * @since 4.0
+ */
+public class MutablePostCommitEntity implements PostCommitEntity {
+
+	private Collection<String> ignoredProperties;
+	private Collection<String> confidentialProperties;
+	private ObjEntity entity;
+
+	public MutablePostCommitEntity(ObjEntity entity) {
+		this.entity = entity;
+		this.ignoredProperties = new HashSet<>();
+		this.confidentialProperties = new HashSet<>();
+	}
+
+	@Override
+	public boolean isIncluded(String property) {
+		return !ignoredProperties.contains(property);
+	}
+
+	@Override
+	public boolean isIncluded() {
+		return true;
+	}
+
+	@Override
+	public boolean isConfidential(String property) {
+		return confidentialProperties.contains(property);
+	}
+
+	MutablePostCommitEntity setConfidential(String[] confidentialProperties) {
+
+		if (confidentialProperties != null) {
+			for (String property : confidentialProperties) {
+				this.confidentialProperties.add(property);
+			}
+		}
+
+		return this;
+	}
+	
+	MutablePostCommitEntity setIgnoreProperties(String[] properties) {
+		if (properties != null) {
+			for (String property : properties) {
+				this.ignoredProperties.add(property);
+			}
+		}
+
+		return this;
+	}
+
+	MutablePostCommitEntity setIgnoreAttributes(boolean ignore) {
+		if (ignore) {
+			for (ObjAttribute a : entity.getAttributes()) {
+				this.ignoredProperties.add(a.getName());
+			}
+		}
+
+		return this;
+	}
+
+	MutablePostCommitEntity setIgnoreToOneRelationships(boolean ignore) {
+		if (ignore) {
+			for (ObjRelationship r : entity.getRelationships()) {
+				if (!r.isToMany()) {
+					this.ignoredProperties.add(r.getName());
+				}
+			}
+		}
+
+		return this;
+	}
+
+	MutablePostCommitEntity setIgnoreToManyRelationships(boolean ignore) {
+		if (ignore) {
+			for (ObjRelationship r : entity.getRelationships()) {
+				if (r.isToMany()) {
+					this.ignoredProperties.add(r.getName());
+				}
+			}
+		}
+
+		return this;
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable3.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable3.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable3.java
new file mode 100644
index 0000000..367cd00
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable3.java
@@ -0,0 +1,11 @@
+package org.apache.cayenne.lifecycle.db;
+
+import org.apache.cayenne.lifecycle.audit.Auditable;
+import org.apache.cayenne.lifecycle.db.auto._Auditable3;
+
+@Auditable(ignoreAttributes = true, ignoreToManyRelationships = true)
+public class Auditable3 extends _Auditable3 {
+
+	private static final long serialVersionUID = 1L;
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable4.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable4.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable4.java
new file mode 100644
index 0000000..b8d594c
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable4.java
@@ -0,0 +1,11 @@
+package org.apache.cayenne.lifecycle.db;
+
+import org.apache.cayenne.lifecycle.audit.Auditable;
+import org.apache.cayenne.lifecycle.db.auto._Auditable4;
+
+@Auditable(ignoreToOneRelationships = true)
+public class Auditable4 extends _Auditable4 {
+
+	private static final long serialVersionUID = 1L;
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/auto/_Auditable3.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/auto/_Auditable3.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/auto/_Auditable3.java
new file mode 100644
index 0000000..544cb71
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/auto/_Auditable3.java
@@ -0,0 +1,51 @@
+package org.apache.cayenne.lifecycle.db.auto;
+
+import java.util.List;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.lifecycle.db.Auditable4;
+
+/**
+ * Class _Auditable3 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Auditable3 extends CayenneDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ID_PK_COLUMN = "ID";
+
+    public static final Property<String> CHAR_PROPERTY1 = new Property<String>("charProperty1");
+    public static final Property<String> CHAR_PROPERTY2 = new Property<String>("charProperty2");
+    public static final Property<List<Auditable4>> AUDITABLE4S = new Property<List<Auditable4>>("auditable4s");
+
+    public void setCharProperty1(String charProperty1) {
+        writeProperty("charProperty1", charProperty1);
+    }
+    public String getCharProperty1() {
+        return (String)readProperty("charProperty1");
+    }
+
+    public void setCharProperty2(String charProperty2) {
+        writeProperty("charProperty2", charProperty2);
+    }
+    public String getCharProperty2() {
+        return (String)readProperty("charProperty2");
+    }
+
+    public void addToAuditable4s(Auditable4 obj) {
+        addToManyTarget("auditable4s", obj, true);
+    }
+    public void removeFromAuditable4s(Auditable4 obj) {
+        removeToManyTarget("auditable4s", obj, true);
+    }
+    @SuppressWarnings("unchecked")
+    public List<Auditable4> getAuditable4s() {
+        return (List<Auditable4>)readProperty("auditable4s");
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/auto/_Auditable4.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/auto/_Auditable4.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/auto/_Auditable4.java
new file mode 100644
index 0000000..eec1048
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/auto/_Auditable4.java
@@ -0,0 +1,46 @@
+package org.apache.cayenne.lifecycle.db.auto;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.lifecycle.db.Auditable3;
+
+/**
+ * Class _Auditable4 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Auditable4 extends CayenneDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ID_PK_COLUMN = "ID";
+
+    public static final Property<String> CHAR_PROPERTY1 = new Property<String>("charProperty1");
+    public static final Property<String> CHAR_PROPERTY2 = new Property<String>("charProperty2");
+    public static final Property<Auditable3> AUDITABLE3 = new Property<Auditable3>("auditable3");
+
+    public void setCharProperty1(String charProperty1) {
+        writeProperty("charProperty1", charProperty1);
+    }
+    public String getCharProperty1() {
+        return (String)readProperty("charProperty1");
+    }
+
+    public void setCharProperty2(String charProperty2) {
+        writeProperty("charProperty2", charProperty2);
+    }
+    public String getCharProperty2() {
+        return (String)readProperty("charProperty2");
+    }
+
+    public void setAuditable3(Auditable3 auditable3) {
+        setToOneTarget("auditable3", auditable3, true);
+    }
+
+    public Auditable3 getAuditable3() {
+        return (Auditable3)readProperty("auditable3");
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_FilteredIT.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_FilteredIT.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_FilteredIT.java
index 5297d2e..229ab43 100644
--- a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_FilteredIT.java
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_FilteredIT.java
@@ -20,7 +20,9 @@ package org.apache.cayenne.lifecycle.postcommit;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -31,11 +33,16 @@ import java.sql.SQLException;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.configuration.server.ServerRuntimeBuilder;
+import org.apache.cayenne.lifecycle.changemap.AttributeChange;
 import org.apache.cayenne.lifecycle.changemap.ChangeMap;
 import org.apache.cayenne.lifecycle.changemap.ObjectChange;
 import org.apache.cayenne.lifecycle.changemap.ObjectChangeType;
-import org.apache.cayenne.lifecycle.changemap.AttributeChange;
+import org.apache.cayenne.lifecycle.changemap.ToManyRelationshipChange;
+import org.apache.cayenne.lifecycle.db.Auditable1;
 import org.apache.cayenne.lifecycle.db.Auditable2;
+import org.apache.cayenne.lifecycle.db.Auditable3;
+import org.apache.cayenne.lifecycle.db.Auditable4;
+import org.apache.cayenne.lifecycle.db.AuditableChild1;
 import org.apache.cayenne.lifecycle.unit.AuditableServerCase;
 import org.apache.cayenne.query.SelectById;
 import org.junit.Before;
@@ -161,4 +168,147 @@ public class PostCommitFilter_FilteredIT extends AuditableServerCase
{
 		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
 	}
 
+	@Test
+	public void testPostCommit_UpdateToMany() throws SQLException {
+		auditable1.insert(1, "xx");
+		auditableChild1.insert(1, 1, "cc1");
+		auditableChild1.insert(2, null, "cc2");
+		auditableChild1.insert(3, null, "cc3");
+
+		final AuditableChild1 ac1 = SelectById.query(AuditableChild1.class, 1).selectOne(context);
+		final AuditableChild1 ac2 = SelectById.query(AuditableChild1.class, 2).selectOne(context);
+		final AuditableChild1 ac3 = SelectById.query(AuditableChild1.class, 3).selectOne(context);
+
+		final Auditable1 a1 = SelectById.query(Auditable1.class, 1).selectOne(context);
+
+		doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) throws Throwable {
+
+				assertSame(context, invocation.getArguments()[0]);
+
+				ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+				assertNotNull(changes);
+				assertEquals(1, changes.getUniqueChanges().size());
+
+				ObjectChange a1c = changes.getChanges().get(new ObjectId("Auditable1", Auditable1.ID_PK_COLUMN,
1));
+				assertNotNull(a1c);
+				assertEquals(ObjectChangeType.UPDATE, a1c.getType());
+				assertEquals(0, a1c.getAttributeChanges().size());
+
+				assertEquals(1, a1c.getToManyRelationshipChanges().size());
+
+				ToManyRelationshipChange a1c1 = a1c.getToManyRelationshipChanges().get(Auditable1.CHILDREN1.getName());
+				assertNotNull(a1c1);
+
+				assertEquals(2, a1c1.getAdded().size());
+				assertTrue(a1c1.getAdded().contains(ac2.getObjectId()));
+				assertTrue(a1c1.getAdded().contains(ac3.getObjectId()));
+
+				assertEquals(1, a1c1.getRemoved().size());
+				assertTrue(a1c1.getRemoved().contains(ac1.getObjectId()));
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		a1.removeFromChildren1(ac1);
+		a1.addToChildren1(ac2);
+		a1.addToChildren1(ac3);
+
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	@Test
+	public void testPostCommit_IgnoreAttributes() throws SQLException {
+
+		auditable3.insert(1, "31", "32");
+
+		final Auditable3 a3 = SelectById.query(Auditable3.class, 1).selectOne(context);
+
+		doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) throws Throwable {
+
+				assertSame(context, invocation.getArguments()[0]);
+
+				ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+				assertNull(changes.getChanges().get(new ObjectId("Auditable3", Auditable3.ID_PK_COLUMN,
1)));
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		a3.setCharProperty1("33");
+		a3.setCharProperty2("34");
+
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	@Test
+	public void testPostCommit_IgnoreToMany() throws SQLException {
+
+		auditable3.insert(1, "31", "32");
+		auditable4.insert(11, "41", "42", 1);
+		auditable4.insert(12, "43", "44", 1);
+
+		final Auditable3 a3 = SelectById.query(Auditable3.class, 1).selectOne(context);
+		final Auditable4 a41 = SelectById.query(Auditable4.class, 11).selectOne(context);
+		final Auditable4 a42 = SelectById.query(Auditable4.class, 12).selectOne(context);
+
+		doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) throws Throwable {
+
+				assertSame(context, invocation.getArguments()[0]);
+
+				ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+				assertNull(changes.getChanges().get(new ObjectId("Auditable3", Auditable3.ID_PK_COLUMN,
1)));
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		a3.removeFromAuditable4s(a41);
+		a3.addToAuditable4s(a42);
+
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	@Test
+	public void testPostCommit_IgnoreToOne() throws SQLException {
+
+		auditable3.insert(1, "31", "32");
+		auditable3.insert(2, "33", "34");
+		auditable4.insert(11, "41", "41", 1);
+
+		final Auditable3 a32 = SelectById.query(Auditable3.class, 2).selectOne(context);
+
+		final Auditable4 a4 = SelectById.query(Auditable4.class, 11).selectOne(context);
+
+		doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) throws Throwable {
+
+				assertSame(context, invocation.getArguments()[0]);
+
+				ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+				assertNull(changes.getChanges().get(new ObjectId("Auditable4", Auditable4.ID_PK_COLUMN,
11)));
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		a4.setAuditable3(a32);
+
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java
index d47da11..ec8024a 100644
--- a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java
@@ -40,6 +40,9 @@ public abstract class AuditableServerCase {
 	protected TableHelper auditableChild3;
 	protected TableHelper auditableChildUuid;
 
+	protected TableHelper auditable3;
+	protected TableHelper auditable4;
+
 	@Before
 	public void startCayenne() throws Exception {
 		this.runtime = configureCayenne().build();
@@ -62,12 +65,18 @@ public abstract class AuditableServerCase {
 		this.auditableChildUuid = new TableHelper(dbHelper, "AUDITABLE_CHILD_UUID").setColumns("ID",
"UUID",
 				"CHAR_PROPERTY1", "CHAR_PROPERTY2");
 
+		this.auditable3 = new TableHelper(dbHelper, "AUDITABLE3").setColumns("ID", "CHAR_PROPERTY1",
"CHAR_PROPERTY2");
+		this.auditable4 = new TableHelper(dbHelper, "AUDITABLE4").setColumns("ID", "CHAR_PROPERTY1",
"CHAR_PROPERTY2",
+				"AUDITABLE3_ID");
+
 		this.auditableChild1.deleteAll();
 		this.auditableChild2.deleteAll();
 		this.auditable1.deleteAll();
 		this.auditableChild3.deleteAll();
 		this.auditable2.deleteAll();
 		this.auditableChildUuid.deleteAll();
+		this.auditable4.deleteAll();
+		this.auditable3.deleteAll();
 	}
 
 	protected ServerRuntimeBuilder configureCayenne() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f0386787/cayenne-lifecycle/src/test/resources/lifecycle-map.map.xml
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/resources/lifecycle-map.map.xml b/cayenne-lifecycle/src/test/resources/lifecycle-map.map.xml
index f254cf7..c04bc31 100644
--- a/cayenne-lifecycle/src/test/resources/lifecycle-map.map.xml
+++ b/cayenne-lifecycle/src/test/resources/lifecycle-map.map.xml
@@ -13,6 +13,17 @@
 		<db-attribute name="CHAR_PROPERTY2" type="VARCHAR" length="200"/>
 		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 	</db-entity>
+	<db-entity name="AUDITABLE3">
+		<db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+		<db-attribute name="CHAR_PROPERTY2" type="VARCHAR" length="200"/>
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+	</db-entity>
+	<db-entity name="AUDITABLE4">
+		<db-attribute name="AUDITABLE3_ID" type="INTEGER"/>
+		<db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+		<db-attribute name="CHAR_PROPERTY2" type="VARCHAR" length="200"/>
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+	</db-entity>
 	<db-entity name="AUDITABLE_CHILD1">
 		<db-attribute name="AUDITABLE1_ID" type="INTEGER"/>
 		<db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
@@ -69,6 +80,14 @@
 		<obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
 		<obj-attribute name="charProperty2" type="java.lang.String" db-attribute-path="CHAR_PROPERTY2"/>
 	</obj-entity>
+	<obj-entity name="Auditable3" className="org.apache.cayenne.lifecycle.db.Auditable3"
dbEntityName="AUDITABLE3">
+		<obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+		<obj-attribute name="charProperty2" type="java.lang.String" db-attribute-path="CHAR_PROPERTY2"/>
+	</obj-entity>
+	<obj-entity name="Auditable4" className="org.apache.cayenne.lifecycle.db.Auditable4"
dbEntityName="AUDITABLE4">
+		<obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+		<obj-attribute name="charProperty2" type="java.lang.String" db-attribute-path="CHAR_PROPERTY2"/>
+	</obj-entity>
 	<obj-entity name="AuditableChild1" className="org.apache.cayenne.lifecycle.db.AuditableChild1"
dbEntityName="AUDITABLE_CHILD1">
 		<obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
 	</obj-entity>
@@ -108,6 +127,12 @@
 	<db-relationship name="children" source="AUDITABLE2" target="AUDITABLE_CHILD3" toMany="true">
 		<db-attribute-pair source="ID" target="AUDITABLE2_ID"/>
 	</db-relationship>
+	<db-relationship name="auditable4s" source="AUDITABLE3" target="AUDITABLE4" toMany="true">
+		<db-attribute-pair source="ID" target="AUDITABLE3_ID"/>
+	</db-relationship>
+	<db-relationship name="auditable3" source="AUDITABLE4" target="AUDITABLE3" toMany="false">
+		<db-attribute-pair source="AUDITABLE3_ID" target="ID"/>
+	</db-relationship>
 	<db-relationship name="parent" source="AUDITABLE_CHILD1" target="AUDITABLE1" toMany="false">
 		<db-attribute-pair source="AUDITABLE1_ID" target="ID"/>
 	</db-relationship>
@@ -137,6 +162,8 @@
 	</db-relationship>
 	<obj-relationship name="children1" source="Auditable1" target="AuditableChild1" deleteRule="Deny"
db-relationship-path="children1"/>
 	<obj-relationship name="children" source="Auditable2" target="AuditableChild3" deleteRule="Deny"
db-relationship-path="children"/>
+	<obj-relationship name="auditable4s" source="Auditable3" target="Auditable4" deleteRule="Deny"
db-relationship-path="auditable4s"/>
+	<obj-relationship name="auditable3" source="Auditable4" target="Auditable3" deleteRule="Nullify"
db-relationship-path="auditable3"/>
 	<obj-relationship name="parent" source="AuditableChild1" target="Auditable1" deleteRule="Nullify"
db-relationship-path="parent"/>
 	<obj-relationship name="parent" source="AuditableChild2" target="Auditable1" deleteRule="Nullify"
db-relationship-path="parent"/>
 	<obj-relationship name="parent" source="AuditableChild3" target="Auditable2" deleteRule="Nullify"
db-relationship-path="parent"/>


Mime
View raw message