cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aadamc...@apache.org
Subject cayenne git commit: PostCommitFilter is confused about changes made by Pre* listeners CAY-2087
Date Tue, 24 May 2016 15:00:31 GMT
Repository: cayenne
Updated Branches:
  refs/heads/master d66188ac7 -> d0580bf18


PostCommitFilter is confused about changes made by Pre* listeners CAY-2087

* fixing by repeatedly refreshing GraphDiff structure as the changes from listeners arrive


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

Branch: refs/heads/master
Commit: d0580bf1887b8f675144efc042516a77134dce59
Parents: d66188a
Author: Andrus Adamchik <andrus@objectstyle.com>
Authored: Tue May 24 16:39:05 2016 +0300
Committer: Andrus Adamchik <andrus@objectstyle.com>
Committed: Tue May 24 17:43:02 2016 +0300

----------------------------------------------------------------------
 ...stCommitFilter_ListenerInducedChangesIT.java | 237 +++++++++++++++++++
 .../cayenne/access/ObjectStoreGraphDiff.java    |  36 +--
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 3 files changed, 259 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/d0580bf1/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_ListenerInducedChangesIT.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_ListenerInducedChangesIT.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_ListenerInducedChangesIT.java
new file mode 100644
index 0000000..7bf0de8
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_ListenerInducedChangesIT.java
@@ -0,0 +1,237 @@
+package org.apache.cayenne.lifecycle.postcommit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.annotation.PrePersist;
+import org.apache.cayenne.annotation.PreUpdate;
+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.db.Auditable1;
+import org.apache.cayenne.lifecycle.db.AuditableChild1;
+import org.apache.cayenne.lifecycle.unit.AuditableServerCase;
+import org.apache.cayenne.query.SelectById;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Testing capturing changes introduced by the pre-commit listeners.
+ */
+public class PostCommitFilter_ListenerInducedChangesIT extends AuditableServerCase {
+
+	protected ObjectContext context;
+	protected PostCommitListener mockListener;
+
+	@Override
+	protected ServerRuntimeBuilder configureCayenne() {
+		this.mockListener = mock(PostCommitListener.class);
+		return super.configureCayenne().addModule(PostCommitModuleBuilder.builder().listener(mockListener).build());
+	}
+
+	@Before
+	public void before() {
+		context = runtime.newContext();
+	}
+
+	@Test
+	public void testPostCommit_Insert() throws SQLException {
+
+		final InsertListener listener = new InsertListener();
+		runtime.getDataDomain().addListener(listener);
+
+		final Auditable1 a1 = context.newObject(Auditable1.class);
+		a1.setCharProperty1("yy");
+
+		doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) throws Throwable {
+
+				assertNotNull(listener.c);
+
+				List<ObjectChange> sortedChanges = sortedChanges(invocation);
+
+				assertEquals(2, sortedChanges.size());
+
+				assertEquals(a1.getObjectId(), sortedChanges.get(0).getPostCommitId());
+				assertEquals(ObjectChangeType.INSERT, sortedChanges.get(0).getType());
+
+				assertEquals(listener.c.getObjectId(), sortedChanges.get(1).getPostCommitId());
+				assertEquals(ObjectChangeType.INSERT, sortedChanges.get(1).getType());
+
+				AttributeChange listenerInducedChange = sortedChanges.get(1).getAttributeChanges()
+						.get(AuditableChild1.CHAR_PROPERTY1.getName());
+				assertNotNull(listenerInducedChange);
+				assertEquals("c1", listenerInducedChange.getNewValue());
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	@Test
+	public void testPostCommit_Delete() throws SQLException {
+
+		auditable1.insert(1, "yy");
+		auditableChild1.insert(31, 1, "yyc");
+
+		final DeleteListener listener = new DeleteListener();
+		runtime.getDataDomain().addListener(listener);
+
+		final Auditable1 a1 = SelectById.query(Auditable1.class, 1).prefetch(Auditable1.CHILDREN1.joint())
+				.selectFirst(context);
+		a1.setCharProperty1("zz");
+
+		doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) throws Throwable {
+
+				assertNotNull(listener.toDelete);
+				assertEquals(1, listener.toDelete.size());
+
+				List<ObjectChange> sortedChanges = sortedChanges(invocation);
+
+				assertEquals(2, sortedChanges.size());
+
+				assertEquals(ObjectChangeType.UPDATE, sortedChanges.get(0).getType());
+				assertEquals(a1.getObjectId(), sortedChanges.get(0).getPostCommitId());
+
+				assertEquals(ObjectChangeType.DELETE, sortedChanges.get(1).getType());
+				assertEquals(listener.toDelete.get(0).getObjectId(), sortedChanges.get(1).getPostCommitId());
+
+				AttributeChange listenerInducedChange = sortedChanges.get(1).getAttributeChanges()
+						.get(AuditableChild1.CHAR_PROPERTY1.getName());
+				assertNotNull(listenerInducedChange);
+				assertEquals("yyc", listenerInducedChange.getOldValue());
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	@Test
+	public void testPostCommit_Update() throws SQLException {
+
+		auditable1.insert(1, "yy");
+		auditableChild1.insert(31, 1, "yyc");
+
+		final UpdateListener listener = new UpdateListener();
+		runtime.getDataDomain().addListener(listener);
+
+		final Auditable1 a1 = SelectById.query(Auditable1.class, 1).prefetch(Auditable1.CHILDREN1.joint())
+				.selectFirst(context);
+		a1.setCharProperty1("zz");
+
+		doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) throws Throwable {
+
+				assertNotNull(listener.toUpdate);
+				assertEquals(1, listener.toUpdate.size());
+
+				List<ObjectChange> sortedChanges = sortedChanges(invocation);
+
+				assertEquals(2, sortedChanges.size());
+
+				assertEquals(ObjectChangeType.UPDATE, sortedChanges.get(0).getType());
+				assertEquals(a1.getObjectId(), sortedChanges.get(0).getPostCommitId());
+
+				assertEquals(ObjectChangeType.UPDATE, sortedChanges.get(1).getType());
+				assertEquals(listener.toUpdate.get(0).getObjectId(), sortedChanges.get(1).getPostCommitId());
+
+				AttributeChange listenerInducedChange = sortedChanges.get(1).getAttributeChanges()
+						.get(AuditableChild1.CHAR_PROPERTY1.getName());
+				assertNotNull(listenerInducedChange);
+				assertEquals("yyc", listenerInducedChange.getOldValue());
+				assertEquals("yyc_", listenerInducedChange.getNewValue());
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	private List<ObjectChange> sortedChanges(InvocationOnMock invocation) {
+		assertSame(context, invocation.getArguments()[0]);
+
+		ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+
+		List<ObjectChange> sortedChanges = new ArrayList<>(changes.getUniqueChanges());
+		Collections.sort(sortedChanges, new Comparator<ObjectChange>() {
+			public int compare(ObjectChange o1, ObjectChange o2) {
+				return o1.getPostCommitId().getEntityName().compareTo(o2.getPostCommitId().getEntityName());
+			}
+		});
+
+		return sortedChanges;
+	}
+
+	static class InsertListener {
+
+		private AuditableChild1 c;
+
+		@PrePersist(Auditable1.class)
+		public void prePersist(Auditable1 a) {
+
+			c = a.getObjectContext().newObject(AuditableChild1.class);
+			c.setCharProperty1("c1");
+			c.setParent(a);
+		}
+	}
+
+	static class DeleteListener {
+
+		private List<AuditableChild1> toDelete;
+
+		@PreUpdate(Auditable1.class)
+		public void prePersist(Auditable1 a) {
+
+			toDelete = new ArrayList<>(a.getChildren1());
+			for (AuditableChild1 c : toDelete) {
+				c.getObjectContext().deleteObject(c);
+			}
+		}
+	}
+
+	static class UpdateListener {
+
+		private List<AuditableChild1> toUpdate;
+
+		@PreUpdate(Auditable1.class)
+		public void prePersist(Auditable1 a) {
+
+			toUpdate = new ArrayList<>(a.getChildren1());
+			for (AuditableChild1 c : toUpdate) {
+				c.setCharProperty1(c.getCharProperty1() + "_");
+			}
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d0580bf1/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
index 18a6174..9a0ffba 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
@@ -48,6 +48,7 @@ class ObjectStoreGraphDiff implements GraphDiff {
 
     private ObjectStore objectStore;
     private GraphDiff resolvedDiff;
+    private int lastSeenDiffId;
 
     ObjectStoreGraphDiff(ObjectStore objectStore) {
         this.objectStore = objectStore;
@@ -147,26 +148,31 @@ class ObjectStoreGraphDiff implements GraphDiff {
      * Converts diffs organized by ObjectId in a collection of diffs sorted by
      * diffId (same as creation order).
      */
-    private void resolveDiff() {
-        if (resolvedDiff == null) {
+	private void resolveDiff() {
 
-            CompoundDiff diff = new CompoundDiff();
-            Map<Object, ObjectDiff> changes = getChangesByObjectId();
+		// refresh the diff on first access or if the underlying ObjectStore has
+		// changed the the last time we cached the changes.
+		if (resolvedDiff == null || lastSeenDiffId < objectStore.currentDiffId) {
 
-            if (!changes.isEmpty()) {
-                List<NodeDiff> allChanges = new ArrayList<NodeDiff>(changes.size()
* 2);
+			CompoundDiff diff = new CompoundDiff();
+			Map<Object, ObjectDiff> changes = getChangesByObjectId();
 
-                for (final ObjectDiff objectDiff : changes.values()) {
-                    objectDiff.appendDiffs(allChanges);
-                }
+			if (!changes.isEmpty()) {
+				List<NodeDiff> allChanges = new ArrayList<NodeDiff>(changes.size() * 2);
 
-                Collections.sort(allChanges);
-                diff.addAll(allChanges);
-            }
+				for (final ObjectDiff objectDiff : changes.values()) {
+					objectDiff.appendDiffs(allChanges);
+				}
 
-            this.resolvedDiff = diff;
-        }
-    }
+				Collections.sort(allChanges);
+				diff.addAll(allChanges);
+
+			}
+
+			this.lastSeenDiffId = objectStore.currentDiffId;
+			this.resolvedDiff = diff;
+		}
+	}
 
     private void preprocess(ObjectStore objectStore) {
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/d0580bf1/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index 00f987e..014b393 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -31,6 +31,7 @@ CAY-2078 Client code gen bug. Unnecessary DataMap class generation setting
datam
 CAY-2080 Cayenne doesn't pick up reverse engineering file changes
 CAY-2084 ObjectIdQuery - no cache access polymorphism
 CAY-2086 SelectById.selectFirst stack overflow
+CAY-2087 PostCommitFilter is confused about changes made by Pre* listeners
 
 ----------------------------------
 Release: 4.0.M3


Mime
View raw message