cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aadamc...@apache.org
Subject [1/2] cayenne git commit: CAY-2030 Capturing a stream of commit changes
Date Fri, 09 Oct 2015 19:44:40 GMT
Repository: cayenne
Updated Branches:
  refs/heads/master 7decb6ce0 -> 58c7c3b37


http://git-wip-us.apache.org/repos/asf/cayenne/blob/58c7c3b3/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/audit/AuditableFilter_InRuntime_Test.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/audit/AuditableFilter_InRuntime_Test.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/audit/AuditableFilter_InRuntime_Test.java
deleted file mode 100644
index 4067e5b..0000000
--- a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/audit/AuditableFilter_InRuntime_Test.java
+++ /dev/null
@@ -1,294 +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.audit;
-
-import org.apache.cayenne.Cayenne;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.access.DataDomain;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.lifecycle.changeset.ChangeSetFilter;
-import org.apache.cayenne.lifecycle.db.Auditable1;
-import org.apache.cayenne.lifecycle.db.Auditable2;
-import org.apache.cayenne.lifecycle.db.AuditableChild1;
-import org.apache.cayenne.lifecycle.db.AuditableChild2;
-import org.apache.cayenne.lifecycle.db.AuditableChild3;
-import org.apache.cayenne.lifecycle.db.AuditableChildUuid;
-import org.apache.cayenne.lifecycle.id.IdCoder;
-import org.apache.cayenne.lifecycle.relationship.ObjectIdRelationshipHandler;
-import org.apache.cayenne.test.jdbc.DBHelper;
-import org.apache.cayenne.test.jdbc.TableHelper;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-public class AuditableFilter_InRuntime_Test {
-
-    private ServerRuntime runtime;
-
-    private TableHelper auditable1;
-    private TableHelper auditableChild1;
-    private TableHelper auditableChild2;
-
-    private TableHelper auditable2;
-    private TableHelper auditableChild3;
-    private TableHelper auditableChildUuid;
-
-    @Before
-    public void setUp() throws Exception {
-        runtime = new ServerRuntime("cayenne-lifecycle.xml");
-
-        DBHelper dbHelper = new DBHelper(runtime.getDataSource(null));
-
-        auditable1 = new TableHelper(dbHelper, "AUDITABLE1").setColumns("ID", "CHAR_PROPERTY1");
-
-        auditableChild1 = new TableHelper(dbHelper, "AUDITABLE_CHILD1").setColumns("ID",
"AUDITABLE1_ID",
-                "CHAR_PROPERTY1");
-
-        auditableChild2 = new TableHelper(dbHelper, "AUDITABLE_CHILD2").setColumns("ID",
"AUDITABLE1_ID",
-                "CHAR_PROPERTY1");
-
-        auditable2 = new TableHelper(dbHelper, "AUDITABLE2").setColumns("ID", "CHAR_PROPERTY1",
"CHAR_PROPERTY2");
-
-        auditableChild3 = new TableHelper(dbHelper, "AUDITABLE_CHILD3").setColumns("ID",
"AUDITABLE2_ID",
-                "CHAR_PROPERTY1", "CHAR_PROPERTY2");
-
-        auditableChildUuid = new TableHelper(dbHelper, "AUDITABLE_CHILD_UUID").setColumns("ID",
"UUID",
-                "CHAR_PROPERTY1", "CHAR_PROPERTY2");
-
-        auditableChild1.deleteAll();
-        auditableChild2.deleteAll();
-        auditable1.deleteAll();
-
-        auditableChild3.deleteAll();
-        auditable2.deleteAll();
-
-        auditableChildUuid.deleteAll();
-    }
-
-    @Test
-    public void testAudit_IgnoreRuntimeRelationships() throws Exception {
-
-        auditable1.insert(1, "xx");
-        auditable1.insert(2, "yy");
-        auditable1.insert(3, "aa");
-        auditableChild2.insert(1, 1, "zz");
-
-        DataDomain domain = runtime.getDataDomain();
-
-        Processor processor = new Processor();
-
-        AuditableFilter filter = new AuditableFilter(domain.getEntityResolver(), processor);
-        domain.addFilter(filter);
-
-        // prerequisite for BaseAuditableProcessor use
-        ChangeSetFilter changeSetFilter = new ChangeSetFilter();
-        domain.addFilter(changeSetFilter);
-
-        ObjectContext context = runtime.newContext();
-
-        Auditable1 a2 = Cayenne.objectForPK(context, Auditable1.class, 2);
-        AuditableChild2 a21 = Cayenne.objectForPK(context, AuditableChild2.class, 1);
-
-        a21.setParent(a2);
-        a21.setCharProperty1("XYZA");
-        context.commitChanges();
-
-        assertEquals(0, processor.size);
-
-        processor.reset();
-
-        Auditable1 a3 = Cayenne.objectForPK(context, Auditable1.class, 3);
-        a21.setParent(a3);
-        a3.setCharProperty1("12");
-
-        context.commitChanges();
-        assertEquals(1, processor.size);
-        assertTrue(processor.audited.get(AuditableOperation.UPDATE).contains(a3));
-    }
-
-    @Test
-    public void testAudit_IncludeToManyRelationships() throws Exception {
-
-        auditable1.insert(1, "xx");
-        auditable1.insert(2, "yy");
-        auditableChild1.insert(1, 1, "zz");
-
-        DataDomain domain = runtime.getDataDomain();
-
-        Processor processor = new Processor();
-
-        AuditableFilter filter = new AuditableFilter(domain.getEntityResolver(), processor);
-        domain.addFilter(filter);
-
-        // prerequisite for BaseAuditableProcessor use
-        ChangeSetFilter changeSetFilter = new ChangeSetFilter();
-        domain.addFilter(changeSetFilter);
-
-        ObjectContext context = runtime.newContext();
-
-        Auditable1 a2 = Cayenne.objectForPK(context, Auditable1.class, 2);
-        AuditableChild1 a21 = Cayenne.objectForPK(context, AuditableChild1.class, 1);
-
-        a21.setParent(a2);
-        context.commitChanges();
-
-        assertEquals(2, processor.size);
-
-        assertTrue(processor.audited.get(AuditableOperation.UPDATE).contains(a2));
-        assertTrue(processor.audited.get(AuditableOperation.UPDATE).contains(
-                Cayenne.objectForPK(context, Auditable1.class, 1)));
-    }
-
-    @Test
-    public void testAudit_IgnoreProperties() throws Exception {
-
-        auditable2.insert(1, "P1_1", "P2_1");
-        auditable2.insert(2, "P1_2", "P2_2");
-        auditable2.insert(3, "P1_3", "P2_3");
-
-        DataDomain domain = runtime.getDataDomain();
-
-        Processor processor = new Processor();
-
-        AuditableFilter filter = new AuditableFilter(domain.getEntityResolver(), processor);
-        domain.addFilter(filter);
-
-        // prerequisite for BaseAuditableProcessor use
-        ChangeSetFilter changeSetFilter = new ChangeSetFilter();
-        domain.addFilter(changeSetFilter);
-
-        ObjectContext context = runtime.newContext();
-
-        Auditable2 a1 = Cayenne.objectForPK(context, Auditable2.class, 1);
-        Auditable2 a2 = Cayenne.objectForPK(context, Auditable2.class, 2);
-        Auditable2 a3 = Cayenne.objectForPK(context, Auditable2.class, 3);
-
-        a1.setCharProperty1("__");
-        a2.setCharProperty2("__");
-        a3.setCharProperty1("__");
-        a3.setCharProperty2("__");
-
-        context.commitChanges();
-
-        assertEquals(2, processor.size);
-        assertTrue(processor.audited.get(AuditableOperation.UPDATE).contains(a2));
-        assertTrue(processor.audited.get(AuditableOperation.UPDATE).contains(a3));
-    }
-
-    @Test
-    public void testAuditableChild_IgnoreProperties() throws Exception {
-
-        auditable2.insert(1, "P1_1", "P2_1");
-        auditable2.insert(2, "P1_2", "P2_2");
-        auditableChild3.insert(1, 1, "C", "D");
-
-        DataDomain domain = runtime.getDataDomain();
-
-        Processor processor = new Processor();
-
-        AuditableFilter filter = new AuditableFilter(domain.getEntityResolver(), processor);
-        domain.addFilter(filter);
-
-        // prerequisite for BaseAuditableProcessor use
-        ChangeSetFilter changeSetFilter = new ChangeSetFilter();
-        domain.addFilter(changeSetFilter);
-
-        ObjectContext context = runtime.newContext();
-
-        AuditableChild3 ac1 = Cayenne.objectForPK(context, AuditableChild3.class, 1);
-
-        // a change to ignored property should not cause an audit event
-        ac1.setCharProperty1("X_X");
-
-        context.commitChanges();
-        assertEquals(0, processor.size);
-
-        processor.reset();
-        ac1.setCharProperty2("XXXXX");
-        context.commitChanges();
-        assertEquals(1, processor.size);
-    }
-
-    @Test
-    public void testAuditableChild_objectIdRelationship() throws Exception {
-        auditable1.insert(1, "xx");
-        auditableChildUuid.insert(1, "Auditable1:1", "xxx", "yyy");
-
-        DataDomain domain = runtime.getDataDomain();
-        Processor processor = new Processor();
-
-        AuditableFilter filter = new AuditableFilter(domain.getEntityResolver(), processor);
-        domain.addFilter(filter);
-
-        // prerequisite for BaseAuditableProcessor use
-        ChangeSetFilter changeSetFilter = new ChangeSetFilter();
-        domain.addFilter(changeSetFilter);
-
-        ObjectContext context = runtime.newContext();
-        AuditableChildUuid ac = Cayenne.objectForPK(context, AuditableChildUuid.class, 1);
-        Auditable1 a1 = Cayenne.objectForPK(context, Auditable1.class, 1);
-        IdCoder refHandler = new IdCoder(domain.getEntityResolver());
-        ObjectIdRelationshipHandler handler = new ObjectIdRelationshipHandler(refHandler);
-        handler.relate(ac, a1);
-
-        ac.setCharProperty1("xxxx");
-        context.commitChanges();
-        assertEquals(1, processor.size);
-        Collection<Object> auditables = processor.audited.get(AuditableOperation.UPDATE);
-        assertSame(a1, auditables.toArray()[0]);
-
-        ac.setCharProperty2("yyyy");
-        context.commitChanges();
-        assertEquals(2, processor.size);
-        assertSame(a1, auditables.toArray()[1]);
-    }
-
-    private final class Processor implements AuditableProcessor {
-
-        Map<AuditableOperation, Collection<Object>> audited;
-        int size;
-
-        Processor() {
-            reset();
-        }
-
-        void reset() {
-
-            audited = new EnumMap<AuditableOperation, Collection<Object>>(AuditableOperation.class);
-
-            for (AuditableOperation op : AuditableOperation.values()) {
-                audited.put(op, new ArrayList<Object>());
-            }
-        }
-
-        public void audit(Persistent object, AuditableOperation operation) {
-            audited.get(operation).add(object);
-            size++;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/58c7c3b3/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable2.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable2.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable2.java
index f1b2c2a..7cce5d8 100644
--- a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable2.java
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/db/Auditable2.java
@@ -21,7 +21,7 @@ package org.apache.cayenne.lifecycle.db;
 import org.apache.cayenne.lifecycle.audit.Auditable;
 import org.apache.cayenne.lifecycle.db.auto._Auditable2;
 
-@Auditable(ignoredProperties = _Auditable2.CHAR_PROPERTY1_PROPERTY)
+@Auditable(ignoredProperties = _Auditable2.CHAR_PROPERTY1_PROPERTY, confidential=_Auditable2.CHAR_PROPERTY2_PROPERTY)
 public class Auditable2 extends _Auditable2 {
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/58c7c3b3/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_AllIT.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_AllIT.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_AllIT.java
new file mode 100644
index 0000000..aef0176
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_AllIT.java
@@ -0,0 +1,295 @@
+/*****************************************************************
+ *   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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+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;
+import static org.mockito.Mockito.verify;
+
+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.ToManyRelationshipChange;
+import org.apache.cayenne.lifecycle.changemap.ToOneRelationshipChange;
+import org.apache.cayenne.lifecycle.db.Auditable1;
+import org.apache.cayenne.lifecycle.db.AuditableChild1;
+import org.apache.cayenne.lifecycle.unit.LifecycleServerCase;
+import org.apache.cayenne.query.SelectById;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class PostCommitFilter_AllIT extends LifecycleServerCase {
+
+	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 Auditable1 a1 = context.newObject(Auditable1.class);
+		a1.setCharProperty1("yy");
+		final ObjectId preCommitId = a1.getObjectId();
+
+		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(2, changes.getChanges().size());
+				assertEquals(1, changes.getUniqueChanges().size());
+
+				ObjectChange c = changes.getUniqueChanges().iterator().next();
+				assertNotNull(c);
+				assertEquals(ObjectChangeType.INSERT, c.getType());
+				assertEquals(1, c.getAttributeChanges().size());
+				assertEquals("yy", c.getAttributeChanges().get(Auditable1.CHAR_PROPERTY1_PROPERTY).getNewValue());
+
+				assertNotEquals(preCommitId, a1.getObjectId());
+				assertEquals(preCommitId, c.getPreCommitId());
+				assertEquals(a1.getObjectId(), c.getPostCommitId());
+
+				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, "xx");
+
+		final Auditable1 a1 = SelectById.query(Auditable1.class, 1).selectOne(context);
+		a1.setCharProperty1("yy");
+
+		final ObjectId preCommitId = a1.getObjectId();
+
+		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 c = changes.getChanges().get(new ObjectId("Auditable1", Auditable1.ID_PK_COLUMN,
1));
+				assertNotNull(c);
+				assertEquals(ObjectChangeType.UPDATE, c.getType());
+				assertEquals(1, c.getAttributeChanges().size());
+				AttributeChange pc = c.getAttributeChanges().get(Auditable1.CHAR_PROPERTY1_PROPERTY);
+				assertNotNull(pc);
+				assertEquals("xx", pc.getOldValue());
+				assertEquals("yy", pc.getNewValue());
+				
+				assertEquals(preCommitId, a1.getObjectId());
+				assertEquals(preCommitId, c.getPreCommitId());
+				assertEquals(preCommitId, c.getPostCommitId());
+
+				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, "xx");
+
+		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 c = changes.getChanges().get(new ObjectId("Auditable1", Auditable1.ID_PK_COLUMN,
1));
+				assertNotNull(c);
+				assertEquals(ObjectChangeType.DELETE, c.getType());
+				assertEquals(1, c.getAttributeChanges().size());
+				assertEquals("xx", c.getAttributeChanges().get(Auditable1.CHAR_PROPERTY1_PROPERTY).getOldValue());
+				assertNull(c.getAttributeChanges().get(Auditable1.CHAR_PROPERTY1_PROPERTY).getNewValue());
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		Auditable1 a1 = SelectById.query(Auditable1.class, 1).selectOne(context);
+		context.deleteObject(a1);
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	@Test
+	public void testPostCommit_UpdateToOne() throws SQLException {
+		auditable1.insert(1, "xx");
+		auditable1.insert(2, "yy");
+
+		auditableChild1.insert(1, 1, "cc1");
+		auditableChild1.insert(2, 2, "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);
+		final Auditable1 a2 = SelectById.query(Auditable1.class, 2).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(4, changes.getUniqueChanges().size());
+
+				ObjectChange ac1c = changes.getChanges()
+						.get(new ObjectId("AuditableChild1", AuditableChild1.ID_PK_COLUMN, 1));
+				assertNotNull(ac1c);
+				assertEquals(ObjectChangeType.UPDATE, ac1c.getType());
+				ToOneRelationshipChange ac1c1 = ac1c.getToOneRelationshipChanges().get(AuditableChild1.PARENT_PROPERTY);
+				assertEquals(a1.getObjectId(), ac1c1.getOldValue());
+				assertEquals(null, ac1c1.getNewValue());
+
+				ObjectChange ac2c = changes.getChanges()
+						.get(new ObjectId("AuditableChild1", AuditableChild1.ID_PK_COLUMN, 2));
+				assertNotNull(ac2c);
+				assertEquals(ObjectChangeType.UPDATE, ac2c.getType());
+				ToOneRelationshipChange ac2c1 = ac2c.getToOneRelationshipChanges().get(AuditableChild1.PARENT_PROPERTY);
+				assertEquals(a2.getObjectId(), ac2c1.getOldValue());
+				assertEquals(a1.getObjectId(), ac2c1.getNewValue());
+
+				ObjectChange ac3c = changes.getChanges()
+						.get(new ObjectId("AuditableChild1", AuditableChild1.ID_PK_COLUMN, 3));
+				assertNotNull(ac3c);
+				assertEquals(ObjectChangeType.UPDATE, ac3c.getType());
+				ToOneRelationshipChange ac3c1 = ac3c.getToOneRelationshipChanges().get(AuditableChild1.PARENT_PROPERTY);
+				assertEquals(null, ac3c1.getOldValue());
+				assertEquals(a1.getObjectId(), ac3c1.getNewValue());
+
+				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_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(4, 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_PROPERTY);
+				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));
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/58c7c3b3/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
new file mode 100644
index 0000000..d49b5ff
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_FilteredIT.java
@@ -0,0 +1,164 @@
+/*****************************************************************
+ *   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;
+
+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 org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.configuration.server.ServerRuntimeBuilder;
+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.db.Auditable2;
+import org.apache.cayenne.lifecycle.unit.LifecycleServerCase;
+import org.apache.cayenne.query.SelectById;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class PostCommitFilter_FilteredIT extends LifecycleServerCase {
+
+	protected ObjectContext context;
+	protected PostCommitListener mockListener;
+
+	@Override
+	protected ServerRuntimeBuilder configureCayenne() {
+		this.mockListener = mock(PostCommitListener.class);
+		return super.configureCayenne()
+				.addModule(PostCommitModuleBuilder.builder().auditableEntitiesOnly().listener(mockListener).build());
+	}
+
+	@Before
+	public void before() {
+		context = runtime.newContext();
+	}
+
+	@Test
+	public void testPostCommit_Insert() throws SQLException {
+
+		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(2, changes.getChanges().size());
+				assertEquals(1, changes.getUniqueChanges().size());
+
+				ObjectChange c = changes.getUniqueChanges().iterator().next();
+				assertNotNull(c);
+				assertEquals(ObjectChangeType.INSERT, c.getType());
+				assertEquals(1, c.getAttributeChanges().size());
+
+				assertEquals(Confidential.getInstance(),
+						c.getAttributeChanges().get(Auditable2.CHAR_PROPERTY2_PROPERTY).getNewValue());
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		Auditable2 a1 = context.newObject(Auditable2.class);
+		a1.setCharProperty1("yy");
+		a1.setCharProperty2("zz");
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	@Test
+	public void testPostCommit_Update() throws SQLException {
+		auditable2.insert(1, "P1_1", "P2_1");
+
+		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 c = changes.getChanges().get(new ObjectId("Auditable2", Auditable2.ID_PK_COLUMN,
1));
+				assertNotNull(c);
+				assertEquals(ObjectChangeType.UPDATE, c.getType());
+				assertEquals(1, c.getAttributeChanges().size());
+				AttributeChange pc = c.getAttributeChanges().get(Auditable2.CHAR_PROPERTY2_PROPERTY);
+				assertNotNull(pc);
+				assertEquals(Confidential.getInstance(), pc.getOldValue());
+				assertEquals(Confidential.getInstance(), pc.getNewValue());
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		Auditable2 a1 = SelectById.query(Auditable2.class, 1).selectOne(context);
+		a1.setCharProperty1("P1_2");
+		a1.setCharProperty2("P2_2");
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+	@Test
+	public void testPostCommit_Delete() throws SQLException {
+		auditable2.insert(1, "P1_1", "P2_1");
+
+		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 c = changes.getChanges().get(new ObjectId("Auditable2", Auditable2.ID_PK_COLUMN,
1));
+				assertNotNull(c);
+				assertEquals(ObjectChangeType.DELETE, c.getType());
+				assertEquals(1, c.getAttributeChanges().size());
+				assertEquals(Confidential.getInstance(),
+						c.getAttributeChanges().get(Auditable2.CHAR_PROPERTY2_PROPERTY).getOldValue());
+
+				return null;
+			}
+		}).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+		Auditable2 a1 = SelectById.query(Auditable2.class, 1).selectOne(context);
+		context.deleteObject(a1);
+		context.commitChanges();
+
+		verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/58c7c3b3/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleBuilderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleBuilderTest.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleBuilderTest.java
new file mode 100644
index 0000000..9340333
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleBuilderTest.java
@@ -0,0 +1,67 @@
+/*****************************************************************
+ *   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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Key;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.lifecycle.changemap.ChangeMap;
+import org.junit.Test;
+
+public class PostCommitModuleBuilderTest {
+
+	@Test
+	public void testListener_Object() {
+
+		L listener = new L();
+		Module m = PostCommitModuleBuilder.builder().listener(listener).build();
+
+		Injector i = DIBootstrap.createInjector(m);
+		List<?> listeners = i.getInstance(Key.get(List.class, PostCommitFilter.POST_COMMIT_LISTENERS_LIST));
+		assertEquals(1, listeners.size());
+		assertTrue(listeners.contains(listener));
+	}
+
+	@Test
+	public void testListener_Class() {
+
+		Module m = PostCommitModuleBuilder.builder().listener(L.class).build();
+
+		Injector i = DIBootstrap.createInjector(m);
+		List<?> listeners = i.getInstance(Key.get(List.class, PostCommitFilter.POST_COMMIT_LISTENERS_LIST));
+		assertEquals(1, listeners.size());
+		assertTrue(listeners.get(0) instanceof L);
+	}
+
+	public static class L implements PostCommitListener {
+
+		@Override
+		public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) {
+			// do nothing.
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/58c7c3b3/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/LifecycleServerCase.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/LifecycleServerCase.java
b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/LifecycleServerCase.java
new file mode 100644
index 0000000..85db299
--- /dev/null
+++ b/cayenne-lifecycle/src/test/java/org/apache/cayenne/lifecycle/unit/LifecycleServerCase.java
@@ -0,0 +1,83 @@
+/*****************************************************************
+ *   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.unit;
+
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.configuration.server.ServerRuntimeBuilder;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * A superclass of integration tests for cayenne-lifecycle.
+ */
+public abstract class LifecycleServerCase {
+
+	protected ServerRuntime runtime;
+
+	protected TableHelper auditable1;
+	protected TableHelper auditableChild1;
+	protected TableHelper auditableChild2;
+
+	protected TableHelper auditable2;
+	protected TableHelper auditableChild3;
+	protected TableHelper auditableChildUuid;
+
+	@Before
+	public void startCayenne() throws Exception {
+		this.runtime = configureCayenne().build();
+
+		DBHelper dbHelper = new DBHelper(runtime.getDataSource());
+
+		this.auditable1 = new TableHelper(dbHelper, "AUDITABLE1").setColumns("ID", "CHAR_PROPERTY1");
+
+		this.auditableChild1 = new TableHelper(dbHelper, "AUDITABLE_CHILD1").setColumns("ID", "AUDITABLE1_ID",
+				"CHAR_PROPERTY1");
+
+		this.auditableChild2 = new TableHelper(dbHelper, "AUDITABLE_CHILD2").setColumns("ID", "AUDITABLE1_ID",
+				"CHAR_PROPERTY1");
+
+		this.auditable2 = new TableHelper(dbHelper, "AUDITABLE2").setColumns("ID", "CHAR_PROPERTY1",
"CHAR_PROPERTY2");
+
+		this.auditableChild3 = new TableHelper(dbHelper, "AUDITABLE_CHILD3").setColumns("ID", "AUDITABLE2_ID",
+				"CHAR_PROPERTY1", "CHAR_PROPERTY2");
+
+		this.auditableChildUuid = new TableHelper(dbHelper, "AUDITABLE_CHILD_UUID").setColumns("ID",
"UUID",
+				"CHAR_PROPERTY1", "CHAR_PROPERTY2");
+
+		this.auditableChild1.deleteAll();
+		this.auditableChild2.deleteAll();
+		this.auditable1.deleteAll();
+		this.auditableChild3.deleteAll();
+		this.auditable2.deleteAll();
+		this.auditableChildUuid.deleteAll();
+	}
+
+	protected ServerRuntimeBuilder configureCayenne() {
+		return ServerRuntimeBuilder.builder().addConfig("cayenne-lifecycle.xml");
+	}
+
+	@After
+	public void shutdownCayenne() {
+		if (runtime != null) {
+			runtime.shutdown();
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/58c7c3b3/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 74daf95..8ebca2b 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -32,6 +32,7 @@ CAY-2023 Decouple the use of ResourceLocator
 CAY-2025 Support for DBCP2
 CAY-2026 Java 7
 CAY-2027 Support for Expression outer join syntax in EJBQL
+CAY-2030 Capturing a stream of commit changes
 
 Bug Fixes:
 


Mime
View raw message