Repository: cayenne Updated Branches: refs/heads/master 8f3ac8b6a -> af5ae7856
http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_ListenerInducedChangesIT.java ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_ListenerInducedChangesIT.java b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_ListenerInducedChangesIT.java new file mode 100644 index 0000000..6f162c3 --- /dev/null +++ b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_ListenerInducedChangesIT.java @@ -0,0 +1,256 @@ +/***************************************************************** + * 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 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/af5ae785/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_OutsideTxIT.java ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_OutsideTxIT.java b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_OutsideTxIT.java new file mode 100644 index 0000000..b9b3278 --- /dev/null +++ b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_OutsideTxIT.java @@ -0,0 +1,85 @@ +/***************************************************************** + * 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.assertNull; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.cayenne.ObjectContext; +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.db.AuditLog; +import org.apache.cayenne.lifecycle.db.Auditable2; +import org.apache.cayenne.lifecycle.unit.AuditableServerCase; +import org.apache.cayenne.tx.BaseTransaction; +import org.junit.Before; +import org.junit.Test; + +public class PostCommitFilter_OutsideTxIT extends AuditableServerCase { + + protected ObjectContext context; + protected PostCommitListener listener; + + @Override + protected ServerRuntimeBuilder configureCayenne() { + this.listener = new PostCommitListener() { + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + + // assert we are inside transaction + assertNull(BaseTransaction.getThreadTransaction()); + + for (ObjectChange c : changes.getUniqueChanges()) { + AuditLog log = runtime.newContext().newObject(AuditLog.class); + log.setLog("DONE: " + c.getPostCommitId()); + log.getObjectContext().commitChanges(); + } + } + }; + return super.configureCayenne().addModule( + PostCommitModuleBuilder.builder().auditableEntitiesOnly().excludeFromTransaction().listener(listener) + .build()); + } + + @Before + public void before() { + this.context = runtime.newContext(); + } + + @Test + public void testCommitLog() throws SQLException { + Auditable2 a1 = context.newObject(Auditable2.class); + a1.setCharProperty1("yy"); + a1.setCharProperty2("zz"); + + Auditable2 a2 = context.newObject(Auditable2.class); + a2.setCharProperty1("yy"); + a2.setCharProperty2("zz"); + context.commitChanges(); + + List<Object[]> logs = auditLog.selectAll(); + assertEquals(2, logs.size()); + } + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_TxIT.java ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_TxIT.java b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_TxIT.java new file mode 100644 index 0000000..8b62be6 --- /dev/null +++ b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitFilter_TxIT.java @@ -0,0 +1,84 @@ +/***************************************************************** + * 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 java.sql.SQLException; +import java.util.List; + +import org.apache.cayenne.ObjectContext; +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.db.AuditLog; +import org.apache.cayenne.lifecycle.db.Auditable2; +import org.apache.cayenne.lifecycle.unit.AuditableServerCase; +import org.apache.cayenne.tx.BaseTransaction; +import org.junit.Before; +import org.junit.Test; + +public class PostCommitFilter_TxIT extends AuditableServerCase { + + protected ObjectContext context; + protected PostCommitListener listener; + + @Override + protected ServerRuntimeBuilder configureCayenne() { + this.listener = new PostCommitListener() { + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + + // assert we are inside transaction + assertNotNull(BaseTransaction.getThreadTransaction()); + + for (ObjectChange c : changes.getUniqueChanges()) { + AuditLog log = runtime.newContext().newObject(AuditLog.class); + log.setLog("DONE: " + c.getPostCommitId()); + log.getObjectContext().commitChanges(); + } + } + }; + return super.configureCayenne().addModule( + PostCommitModuleBuilder.builder().auditableEntitiesOnly().listener(listener).build()); + } + + @Before + public void before() { + this.context = runtime.newContext(); + } + + @Test + public void testCommitLog() throws SQLException { + Auditable2 a1 = context.newObject(Auditable2.class); + a1.setCharProperty1("yy"); + a1.setCharProperty2("zz"); + + Auditable2 a2 = context.newObject(Auditable2.class); + a2.setCharProperty1("yy"); + a2.setCharProperty2("zz"); + context.commitChanges(); + + List<Object[]> logs = auditLog.selectAll(); + assertEquals(2, logs.size()); + } + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleBuilderTest.java ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleBuilderTest.java b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleBuilderTest.java new file mode 100644 index 0000000..48a9d6c --- /dev/null +++ b/cayenne-postcommit/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<PostCommitListener> listeners = i.getInstance(Key.getListOf(PostCommitListener.class)); + 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<PostCommitListener> listeners = i.getInstance(Key.getListOf(PostCommitListener.class)); + 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/af5ae785/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleProviderTest.java ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleProviderTest.java b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleProviderTest.java new file mode 100644 index 0000000..cbf8152 --- /dev/null +++ b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/postcommit/PostCommitModuleProviderTest.java @@ -0,0 +1,32 @@ +/***************************************************************** + * 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 org.apache.cayenne.configuration.server.CayenneServerModuleProvider; +import org.apache.cayenne.unit.util.ModuleProviderChecker; +import org.junit.Test; + +public class PostCommitModuleProviderTest { + + @Test + public void testAutoLoadable() { + ModuleProviderChecker.testProviderPresent(PostCommitServerModuleProvider.class, CayenneServerModuleProvider.class); + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java new file mode 100644 index 0000000..2fb057b --- /dev/null +++ b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/unit/AuditableServerCase.java @@ -0,0 +1,88 @@ +/***************************************************************** + * 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 AuditableServerCase { + + protected ServerRuntime runtime; + + protected TableHelper auditable1; + protected TableHelper auditableChild1; + + protected TableHelper auditable2; + protected TableHelper auditableChild3; + + protected TableHelper auditable3; + protected TableHelper auditable4; + + protected TableHelper auditLog; + + @Before + public void startCayenne() throws Exception { + this.runtime = configureCayenne().build(); + + DBHelper dbHelper = new DBHelper(runtime.getDataSource()); + + this.auditLog = new TableHelper(dbHelper, "AUDIT_LOG").setColumns("ID", "LOG"); + + this.auditable1 = new TableHelper(dbHelper, "AUDITABLE1").setColumns("ID", "CHAR_PROPERTY1"); + + this.auditableChild1 = new TableHelper(dbHelper, "AUDITABLE_CHILD1").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.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.auditable1.deleteAll(); + this.auditableChild3.deleteAll(); + this.auditable2.deleteAll(); + this.auditable4.deleteAll(); + this.auditable3.deleteAll(); + + this.auditLog.deleteAll(); + } + + protected ServerRuntimeBuilder configureCayenne() { + return ServerRuntime.builder().addConfig("cayenne-lifecycle.xml"); + } + + @After + public void shutdownCayenne() { + if (runtime != null) { + runtime.shutdown(); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/unit/FlattenedServerCase.java ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/unit/FlattenedServerCase.java b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/unit/FlattenedServerCase.java new file mode 100644 index 0000000..b9d1b0c --- /dev/null +++ b/cayenne-postcommit/src/test/java/org/apache/cayenne/lifecycle/unit/FlattenedServerCase.java @@ -0,0 +1,62 @@ +/***************************************************************** + * 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; + +public class FlattenedServerCase { + + protected ServerRuntime runtime; + + protected TableHelper e3; + protected TableHelper e4; + protected TableHelper e34; + + @Before + public void startCayenne() throws Exception { + this.runtime = configureCayenne().build(); + + DBHelper dbHelper = new DBHelper(runtime.getDataSource()); + + this.e3 = new TableHelper(dbHelper, "E3").setColumns("ID"); + this.e4 = new TableHelper(dbHelper, "E4").setColumns("ID"); + this.e34 = new TableHelper(dbHelper, "E34").setColumns("E3_ID", "E4_ID"); + + this.e34.deleteAll(); + this.e3.deleteAll(); + + } + + protected ServerRuntimeBuilder configureCayenne() { + return ServerRuntime.builder().addConfig("cayenne-lifecycle.xml"); + } + + @After + public void shutdownCayenne() { + if (runtime != null) { + runtime.shutdown(); + } + } + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-postcommit/src/test/resources/cayenne-lifecycle.xml ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/resources/cayenne-lifecycle.xml b/cayenne-postcommit/src/test/resources/cayenne-lifecycle.xml new file mode 100644 index 0000000..5b9a83e --- /dev/null +++ b/cayenne-postcommit/src/test/resources/cayenne-lifecycle.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<domain project-version="9"> + <map name="lifecycle-map"/> + + <node name="lifecycle-db" + factory="org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory" + schema-update-strategy="org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy" + > + <map-ref name="lifecycle-map"/> + <data-source> + <driver value="org.hsqldb.jdbcDriver"/> + <url value="jdbc:hsqldb:mem:lifecycle"/> + <connectionPool min="1" max="1"/> + <login userName="sa"/> + </data-source> + </node> +</domain> http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-postcommit/src/test/resources/lifecycle-map.map.xml ---------------------------------------------------------------------- diff --git a/cayenne-postcommit/src/test/resources/lifecycle-map.map.xml b/cayenne-postcommit/src/test/resources/lifecycle-map.map.xml new file mode 100644 index 0000000..d7a4a4a --- /dev/null +++ b/cayenne-postcommit/src/test/resources/lifecycle-map.map.xml @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ 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. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> + +<data-map xmlns="http://cayenne.apache.org/schema/9/modelMap" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://cayenne.apache.org/schema/9/modelMap http://cayenne.apache.org/schema/9/modelMap.xsd" + project-version="9"> + <property name="defaultPackage" value="org.apache.cayenne.lifecycle.db"/> + <db-entity name="AUDITABLE1"> + <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/> + <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/> + </db-entity> + <db-entity name="AUDITABLE2"> + <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="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"/> + <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/> + </db-entity> + <db-entity name="AUDITABLE_CHILD3"> + <db-attribute name="AUDITABLE2_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="AUDIT_LOG"> + <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/> + <db-attribute name="LOG" type="CLOB"/> + </db-entity> + <db-entity name="E1"> + <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/> + </db-entity> + <db-entity name="E2"> + <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/> + </db-entity> + <db-entity name="E3"> + <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/> + </db-entity> + <db-entity name="E34"> + <db-attribute name="E3_ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/> + <db-attribute name="E4_ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/> + </db-entity> + <db-entity name="E4"> + <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/> + </db-entity> + <obj-entity name="AuditLog" className="org.apache.cayenne.lifecycle.db.AuditLog" dbEntityName="AUDIT_LOG"> + <obj-attribute name="log" type="java.lang.String" db-attribute-path="LOG"/> + </obj-entity> + <obj-entity name="Auditable1" className="org.apache.cayenne.lifecycle.db.Auditable1" dbEntityName="AUDITABLE1"> + <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/> + </obj-entity> + <obj-entity name="Auditable2" className="org.apache.cayenne.lifecycle.db.Auditable2" dbEntityName="AUDITABLE2"> + <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> + <obj-entity name="AuditableChild3" className="org.apache.cayenne.lifecycle.db.AuditableChild3" dbEntityName="AUDITABLE_CHILD3"> + <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="E1" className="org.apache.cayenne.lifecycle.db.E1" dbEntityName="E1"> + </obj-entity> + <obj-entity name="E2" className="org.apache.cayenne.lifecycle.db.E2" dbEntityName="E2"> + </obj-entity> + <obj-entity name="E3" className="org.apache.cayenne.lifecycle.db.E3" dbEntityName="E3"> + </obj-entity> + <obj-entity name="E4" className="org.apache.cayenne.lifecycle.db.E4" dbEntityName="E4"> + </obj-entity> + <db-relationship name="children1" source="AUDITABLE1" target="AUDITABLE_CHILD1" toMany="true"> + <db-attribute-pair source="ID" target="AUDITABLE1_ID"/> + </db-relationship> + <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> + <db-relationship name="parent" source="AUDITABLE_CHILD3" target="AUDITABLE2" toMany="false"> + <db-attribute-pair source="AUDITABLE2_ID" target="ID"/> + </db-relationship> + <db-relationship name="e34s" source="E3" target="E34" toDependentPK="true" toMany="true"> + <db-attribute-pair source="ID" target="E3_ID"/> + </db-relationship> + <db-relationship name="e3" source="E34" target="E3" toMany="false"> + <db-attribute-pair source="E3_ID" target="ID"/> + </db-relationship> + <db-relationship name="e4" source="E34" target="E4" toMany="false"> + <db-attribute-pair source="E4_ID" target="ID"/> + </db-relationship> + <db-relationship name="e34s" source="E4" target="E34" toDependentPK="true" toMany="true"> + <db-attribute-pair source="ID" target="E4_ID"/> + </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="AuditableChild3" target="Auditable2" deleteRule="Nullify" db-relationship-path="parent"/> + <obj-relationship name="e4s" source="E3" target="E4" deleteRule="Deny" db-relationship-path="e34s.e4"/> + <obj-relationship name="e3s" source="E4" target="E3" deleteRule="Deny" db-relationship-path="e34s.e3"/> +</data-map> http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/main/java/org/apache/cayenne/ashwood/SortWeight.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/ashwood/SortWeight.java b/cayenne-server/src/main/java/org/apache/cayenne/ashwood/SortWeight.java new file mode 100644 index 0000000..5719a99 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/ashwood/SortWeight.java @@ -0,0 +1,45 @@ +/***************************************************************** + * 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.ashwood; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that defines the insertion sorting "weight" of an entity that is used + * when sorting DB operations. This annotation allows to override the topological sorting + * algorithm used by Cayenne by default in special occasions. + * + * @since 3.1, since 4.0 moved to cayenne-server from cayenne-lifecycle + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SortWeight { + + /** + * Returns the "weight" of the entity used for the purpose of the DB operations + * sorting. Entities with lower values will be inserted before entities with higher + * values. The opposite is true for the delete operations. + */ + int value() default 1; +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/main/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorter.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorter.java b/cayenne-server/src/main/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorter.java new file mode 100644 index 0000000..99df6a6 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorter.java @@ -0,0 +1,120 @@ +/***************************************************************** + * 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.ashwood; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.ObjEntity; +import org.apache.commons.collections.comparators.ReverseComparator; + +/** + * EntitySorter that takes into account entity "weights", and otherwise delegating to + * another (topological) sorter. + * + * @since 3.1, since 4.0 moved to cayenne-server from cayenne-lifecycle + */ +public class WeightedAshwoodEntitySorter extends AshwoodEntitySorter { + + private Comparator<DbEntity> weightedDbEntityComparator; + private Comparator<ObjEntity> weightedObjEntityComparator; + + protected Map<DbEntity, Integer> entityWeights; + + public WeightedAshwoodEntitySorter() { + this.weightedDbEntityComparator = new WeightedDbEntityComparator(); + this.weightedObjEntityComparator = new WeightedObjEntityComparator(); + this.entityWeights = Collections.emptyMap(); + } + + @Override + protected void doIndexSorter() { + super.doIndexSorter(); + + entityWeights = new HashMap<>(); + + for (ObjEntity entity : entityResolver.getObjEntities()) { + addWeightForEntity(entity); + } + } + + protected void addWeightForEntity(ObjEntity entity) { + Class<?> type = entityResolver + .getClassDescriptor(entity.getName()) + .getObjectClass(); + SortWeight weight = type.getAnnotation(SortWeight.class); + if (weight != null) { + entityWeights.put(entity.getDbEntity(), weight.value()); + } + } + + @SuppressWarnings("unchecked") + @Override + protected Comparator<DbEntity> getDbEntityComparator(boolean dependantFirst) { + Comparator<DbEntity> c = weightedDbEntityComparator; + if (dependantFirst) { + c = new ReverseComparator(c); + } + return c; + } + + @SuppressWarnings("unchecked") + @Override + protected Comparator<ObjEntity> getObjEntityComparator(boolean dependantFirst) { + Comparator<ObjEntity> c = weightedObjEntityComparator; + if (dependantFirst) { + c = new ReverseComparator(c); + } + return c; + } + + private int getWeight(DbEntity e) { + Integer w = entityWeights.get(e); + return w != null ? w : 1; + } + + private final class WeightedDbEntityComparator implements Comparator<DbEntity> { + + public int compare(DbEntity t1, DbEntity t2) { + if (t1 == t2) { + return 0; + } + + int delta = getWeight(t1) - getWeight(t2); + return delta != 0 ? delta : dbEntityComparator.compare(t1, t2); + } + } + + private final class WeightedObjEntityComparator implements Comparator<ObjEntity> { + + public int compare(ObjEntity o1, ObjEntity o2) { + if (o1 == o2) { + return 0; + } + + DbEntity t1 = o1.getDbEntity(); + DbEntity t2 = o2.getDbEntity(); + + return weightedDbEntityComparator.compare(t1, t2); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorterIT.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorterIT.java b/cayenne-server/src/test/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorterIT.java new file mode 100644 index 0000000..4f50aea --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorterIT.java @@ -0,0 +1,70 @@ +/***************************************************************** + * 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.ashwood; + +import java.util.Arrays; +import java.util.List; + +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.unit.di.server.CayenneProjects; +import org.apache.cayenne.unit.di.server.ServerCase; +import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +@UseServerRuntime(CayenneProjects.WEIGHTED_SORT_PROJECT) +public class WeightedAshwoodEntitySorterIT extends ServerCase { + + @Inject + protected ObjectContext context; + + EntityResolver resolver; + + @Before + public void setUp() throws Exception { + this.resolver = context.getEntityResolver(); + } + + @Test + public void testSortDbEntities() { + // since it is impossible to ensure non-coincidental sort order of unrelated + // DbEntities (without overriding DbEntity.hashCode()), we'll test on 2 entities + // with a relationship, and reverse the topological order with SortWeight annotation. + + List<DbEntity> eSorted = Arrays.asList(resolver.getDbEntity("SORT_DEP"), resolver.getDbEntity("SORT_ROOT")); + + List<DbEntity> e1 = Arrays.asList(resolver.getDbEntity("SORT_ROOT"), resolver.getDbEntity("SORT_DEP")); + + List<DbEntity> e2 = Arrays.asList(resolver.getDbEntity("SORT_DEP"), resolver.getDbEntity("SORT_ROOT")); + + WeightedAshwoodEntitySorter sorter = new WeightedAshwoodEntitySorter(); + sorter.setEntityResolver(resolver); + + sorter.sortDbEntities(e1, false); + assertEquals(eSorted, e1); + + sorter.sortDbEntities(e2, false); + assertEquals(eSorted, e2); + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/SortDep.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/SortDep.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/SortDep.java new file mode 100644 index 0000000..3d26ace --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/SortDep.java @@ -0,0 +1,9 @@ +package org.apache.cayenne.testdo.weighted_sort; + +import org.apache.cayenne.testdo.weighted_sort.auto._SortDep; + +public class SortDep extends _SortDep { + + private static final long serialVersionUID = 1L; + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/SortRoot.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/SortRoot.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/SortRoot.java new file mode 100644 index 0000000..cb0bdc4 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/SortRoot.java @@ -0,0 +1,11 @@ +package org.apache.cayenne.testdo.weighted_sort; + +import org.apache.cayenne.ashwood.SortWeight; +import org.apache.cayenne.testdo.weighted_sort.auto._SortRoot; + +@SortWeight(2) +public class SortRoot extends _SortRoot { + + private static final long serialVersionUID = 1L; + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/auto/_SortDep.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/auto/_SortDep.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/auto/_SortDep.java new file mode 100644 index 0000000..3f6f3ca --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/auto/_SortDep.java @@ -0,0 +1,30 @@ +package org.apache.cayenne.testdo.weighted_sort.auto; + +import org.apache.cayenne.CayenneDataObject; +import org.apache.cayenne.exp.Property; +import org.apache.cayenne.testdo.weighted_sort.SortRoot; + +/** + * Class _SortDep 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 _SortDep extends CayenneDataObject { + + private static final long serialVersionUID = 1L; + + public static final String ID_PK_COLUMN = "ID"; + + public static final Property<SortRoot> ROOT = Property.create("root", SortRoot.class); + + public void setRoot(SortRoot root) { + setToOneTarget("root", root, true); + } + + public SortRoot getRoot() { + return (SortRoot)readProperty("root"); + } + + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/auto/_SortRoot.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/auto/_SortRoot.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/auto/_SortRoot.java new file mode 100644 index 0000000..06d8d60 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/weighted_sort/auto/_SortRoot.java @@ -0,0 +1,35 @@ +package org.apache.cayenne.testdo.weighted_sort.auto; + +import java.util.List; + +import org.apache.cayenne.CayenneDataObject; +import org.apache.cayenne.exp.Property; +import org.apache.cayenne.testdo.weighted_sort.SortDep; + +/** + * Class _SortRoot 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 _SortRoot extends CayenneDataObject { + + private static final long serialVersionUID = 1L; + + public static final String ID_PK_COLUMN = "ID"; + + public static final Property<List<SortDep>> DEPS = Property.create("deps", List.class); + + public void addToDeps(SortDep obj) { + addToManyTarget("deps", obj, true); + } + public void removeFromDeps(SortDep obj) { + removeToManyTarget("deps", obj, true); + } + @SuppressWarnings("unchecked") + public List<SortDep> getDeps() { + return (List<SortDep>)readProperty("deps"); + } + + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java index 5710bcd..0cd6372 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java @@ -79,5 +79,5 @@ public class CayenneProjects { public static final String UNSUPPORTED_DISTINCT_TYPES_PROJECT = "cayenne-unsupported-distinct-types.xml"; public static final String UUID_PROJECT = "cayenne-uuid.xml"; public static final String CUSTOM_NAME_PROJECT = "custom-name-file.xml"; - + public static final String WEIGHTED_SORT_PROJECT = "cayenne-weighted-sort.xml"; } http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java index ec5d332..00121a5 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java @@ -80,7 +80,7 @@ public class SchemaBuilder { "table-primitives.map.xml", "generic.map.xml", "map-db1.map.xml", "map-db2.map.xml", "embeddable.map.xml", "qualified.map.xml", "quoted-identifiers.map.xml", "inheritance-single-table1.map.xml", "inheritance-vertical.map.xml", "oneway-rels.map.xml", "unsupported-distinct-types.map.xml", - "array-type.map.xml", "cay-2032.map.xml" }; + "array-type.map.xml", "cay-2032.map.xml", "weighted-sort.map.xml" }; // hardcoded dependent entities that should be excluded // if LOBs are not supported @@ -246,7 +246,7 @@ public class SchemaBuilder { protected List<DbEntity> dbEntitiesInDeleteOrder(DataMap dataMap) { DataMap map = domain.getDataMap(dataMap.getName()); - List<DbEntity> entities = new ArrayList<DbEntity>(map.getDbEntities()); + List<DbEntity> entities = new ArrayList<>(map.getDbEntities()); dbEntitiesFilter(entities); http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/resources/cayenne-weighted-sort.xml ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/resources/cayenne-weighted-sort.xml b/cayenne-server/src/test/resources/cayenne-weighted-sort.xml new file mode 100644 index 0000000..cc939f7 --- /dev/null +++ b/cayenne-server/src/test/resources/cayenne-weighted-sort.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<domain project-version="9"> + <map name="weighted-sort"/> +</domain> http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/cayenne-server/src/test/resources/weighted-sort.map.xml ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/resources/weighted-sort.map.xml b/cayenne-server/src/test/resources/weighted-sort.map.xml new file mode 100644 index 0000000..ac01ec5 --- /dev/null +++ b/cayenne-server/src/test/resources/weighted-sort.map.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<data-map xmlns="http://cayenne.apache.org/schema/9/modelMap" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://cayenne.apache.org/schema/9/modelMap http://cayenne.apache.org/schema/9/modelMap.xsd" + project-version="9"> + <property name="defaultPackage" value="org.apache.cayenne.testdo.weighted_sort"/> + <db-entity name="SORT_DEP"> + <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/> + <db-attribute name="ROOT_ID" type="BIGINT" isMandatory="true"/> + </db-entity> + <db-entity name="SORT_ROOT"> + <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/> + </db-entity> + <obj-entity name="SortDep" className="org.apache.cayenne.testdo.weighted_sort.SortDep" dbEntityName="SORT_DEP"> + </obj-entity> + <obj-entity name="SortRoot" className="org.apache.cayenne.testdo.weighted_sort.SortRoot" dbEntityName="SORT_ROOT"> + </obj-entity> + <db-relationship name="root" source="SORT_DEP" target="SORT_ROOT" toMany="false"> + <db-attribute-pair source="ROOT_ID" target="ID"/> + </db-relationship> + <db-relationship name="deps" source="SORT_ROOT" target="SORT_DEP" toMany="true"> + <db-attribute-pair source="ID" target="ROOT_ID"/> + </db-relationship> + <obj-relationship name="root" source="SortDep" target="SortRoot" deleteRule="Nullify" db-relationship-path="root"/> + <obj-relationship name="deps" source="SortRoot" target="SortDep" deleteRule="Deny" db-relationship-path="deps"/> +</data-map> http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/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 694e754..64ff8fa 100644 --- a/docs/doc/src/main/resources/RELEASE-NOTES.txt +++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt @@ -20,6 +20,7 @@ CAY-2255 ObjectSelect improvement: columns as full entities CAY-2258 DI: type-safe binding of List and Map CAY-2259 QueryCache: support for referencing type-safe caches CAY-2261 Replace NamedQuery with MappedXYZ in *datamap.vm +CAY-2262 Module auto-loading CAY-2266 Move EventBridge implementations into autoloadable modules CAY-2267 Contribute lifecycle events listeners via DI CAY-2268 DI: Refactor ListBuilder API ambiguities for before() / after() bindings @@ -29,6 +30,7 @@ CAY-2271 ColumnSelect: support for prefetch and limit CAY-2272 ColumnSelect: methods to manually control DISTINCT clause CAY-2274 Modeler: Validate case when dependent PK is marked as âgeneratedâ CAY-2277 Create ClientRuntime with ClientRuntimeBuilder just like ServerRuntime +CAY-2278 Extract cayenne-postcommit module from cayenne-lifecycle Bug Fixes: http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/docs/doc/src/main/resources/UPGRADE.txt ---------------------------------------------------------------------- diff --git a/docs/doc/src/main/resources/UPGRADE.txt b/docs/doc/src/main/resources/UPGRADE.txt index d73c353..f3c538b 100644 --- a/docs/doc/src/main/resources/UPGRADE.txt +++ b/docs/doc/src/main/resources/UPGRADE.txt @@ -7,6 +7,13 @@ IMPORTANT: be sure to read all notes for the intermediate releases between your UPGRADING TO 4.0.M6 +* Per CAY-2278 The org.apache.cayenne.lifecycle.audit package (with the exception of AuditableChild annotation) and the + org.apache.cayenne.lifecycle.changeset package where deprecated. + Weighted graph sorter moved to cayenne-server into org.apache.cayenne.ashwood package. + Packages org.apache.cayenne.lifecycle.changemap, org.apache.cayenne.lifecycle.postcommit and Auditable annotation + where moved to the new cayenne-postcommit module. + Please change your code accordingly. + * Per CAY-2277 ClientRuntime created with ClientRuntimeBuilder, direct instantiation of ClientRuntime is deprecated. Also whole ClientLocalRuntime class is deprecated, use instead ClientRuntimeBuilder.local() method. @@ -19,9 +26,10 @@ UPGRADING TO 4.0.M6 - cayenne-client-jetty - cayenne-protostuff (it also supports auto-loading by ServerRuntimeBuilder) - Also new modules are introduced and should be added to your pom.xml if the corresponding + Also new modules are extracted from the existing one and should be added to your pom.xml if the corresponding functionality is used by your project: - - cayenne-cache-invalidation (moved from cayenne-lifecycle) + - cayenne-cache-invalidation (was part of cayenne-lifecycle) + - cayenne-postcommit (was part of cayenne-lifecycle) * Per CAY-2259 InvalidationFunction returns CacheGroupDescriptor instead of simple String with cache group name, change your custom functions accordingly. http://git-wip-us.apache.org/repos/asf/cayenne/blob/af5ae785/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index a3d82b6..f938dcb 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,7 @@ <module>cayenne-ant</module> <module>cayenne-project</module> <module>cayenne-lifecycle</module> + <module>cayenne-postcommit</module> <module>cayenne-crypto</module> <module>cayenne-joda</module> <module>cayenne-dbcp2</module>