/***************************************************************** * 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.access; import org.apache.cayenne.Cayenne; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.PersistenceState; import org.apache.cayenne.Persistent; import org.apache.cayenne.configuration.server.ServerRuntime; import org.apache.cayenne.di.Inject; import org.apache.cayenne.map.ObjEntity; import org.apache.cayenne.map.ObjRelationship; import org.apache.cayenne.query.SelectQuery; import org.apache.cayenne.test.jdbc.DBHelper; import org.apache.cayenne.test.jdbc.TableHelper; import org.apache.cayenne.testdo.testmap.ArtGroup; import org.apache.cayenne.testdo.testmap.Artist; import org.apache.cayenne.testdo.testmap.Painting; import org.apache.cayenne.testdo.testmap.PaintingInfo; import org.apache.cayenne.unit.di.DataChannelInterceptor; import org.apache.cayenne.unit.di.UnitTestClosure; 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 java.sql.Types; import java.util.Collection; import java.util.HashMap; import java.util.List; import static org.junit.Assert.*; @UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) public class NestedDataContextWriteIT extends ServerCase { @Inject private ServerRuntime runtime; @Inject private DataContext context; @Inject private DataChannelInterceptor queryInterceptor; @Inject private DBHelper dbHelper; private TableHelper tArtist; private TableHelper tPainting; private TableHelper tPaintingInfo; @Before public void setUp() throws Exception { tArtist = new TableHelper(dbHelper, "ARTIST"); tArtist.setColumns("ARTIST_ID", "ARTIST_NAME"); tPainting = new TableHelper(dbHelper, "PAINTING"); tPainting.setColumns( "PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "ESTIMATED_PRICE").setColumnTypes( Types.INTEGER, Types.VARCHAR, Types.BIGINT, Types.DECIMAL); tPaintingInfo = new TableHelper(dbHelper, "PAINTING_INFO"); tPaintingInfo.setColumns("PAINTING_ID", "TEXT_REVIEW", "IMAGE_BLOB"); } private void createArtistsDataSet() throws Exception { tArtist.insert(33001, "artist1"); tArtist.insert(33002, "artist2"); tArtist.insert(33003, "artist3"); tArtist.insert(33004, "artist4"); } private void createMixedDataSet() throws Exception { tArtist.insert(33001, "artist1"); tArtist.insert(33002, "artist2"); tArtist.insert(33003, "artist3"); tArtist.insert(33004, "artist4"); tPainting.insert(33001, "P_artist1", 33001, 3000); tPainting.insert(33002, "P_artist2", 33002, 3000); tPainting.insert(33003, "P_artist3", 33003, 3000); tPainting.insert(33004, "P_artist4", 33004, 3000); tPainting.insert(33005, "P_artist5", null, 3000); tPainting.insert(33006, "P_artist6", 33001, 3000); } private void createNullifyToOneDataSet() throws Exception { tArtist.insert(33001, "artist1"); tPainting.insert(33001, "P_artist1", 33001, 3000); } private void createSingleArtistDataSet() throws Exception { tArtist.insert(33001, "artist1"); } /** * Ensures that created DataContext's ObjectStore retains unreferenced registered * objects. */ // TODO : pluggable retain strategy private DataContext createDataContext() { context.getObjectStore().objectMap = new HashMap<Object, Persistent>(); return context; } @Test public void testDeleteNew() throws Exception { createSingleArtistDataSet(); DataContext context = createDataContext(); ObjectContext childContext = runtime.newContext(context); Artist a = Cayenne.objectForPK(childContext, Artist.class, 33001); Painting p = childContext.newObject(Painting.class); p.setPaintingTitle("X"); a.addToPaintingArray(p); childContext.commitChangesToParent(); childContext.deleteObjects(p); a.removeFromPaintingArray(p); childContext.commitChangesToParent(); } /** * A test case for CAY-698 bug. */ @Test public void testNullifyToOne() throws Exception { createNullifyToOneDataSet(); final DataContext context = createDataContext(); final ObjectContext childContext = runtime.newContext(context); ObjectContext childContextPeer = runtime.newContext(context); final Painting childP1 = Cayenne.objectForPK(childContext, Painting.class, 33001); // trigger object creation in the peer nested DC Cayenne.objectForPK(childContextPeer, Painting.class, 33001); childP1.setToArtist(null); queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() { public void execute() { childContext.commitChangesToParent(); assertEquals(PersistenceState.COMMITTED, childP1.getPersistenceState()); Painting parentP1 = (Painting) context.getGraphManager().getNode( childP1.getObjectId()); assertNotNull(parentP1); assertEquals(PersistenceState.MODIFIED, parentP1.getPersistenceState()); assertNull(parentP1.getToArtist()); } }); } @Test public void testCommitChangesToParent() throws Exception { createArtistsDataSet(); final DataContext context = createDataContext(); final ObjectContext childContext = runtime.newContext(context); // make sure we fetch in predictable order SelectQuery query = new SelectQuery(Artist.class); query.addOrdering(Artist.ARTIST_NAME.asc()); List<?> objects = childContext.performQuery(query); assertEquals(4, objects.size()); final Artist childNew = childContext.newObject(Artist.class); childNew.setArtistName("NNN"); final Artist childModified = (Artist) objects.get(0); childModified.setArtistName("MMM"); final Artist childCommitted = (Artist) objects.get(1); final Artist childHollow = (Artist) objects.get(3); childContext.invalidateObjects(childHollow); queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() { public void execute() { childContext.commitChangesToParent(); // * all modified child objects must be in committed state now // * all modifications should be propagated to the parent // * no actual commit should occur. assertEquals(PersistenceState.COMMITTED, childNew.getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childModified .getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childCommitted .getPersistenceState()); assertEquals(PersistenceState.HOLLOW, childHollow.getPersistenceState()); Artist parentNew = (Artist) context.getGraphManager().getNode( childNew.getObjectId()); Artist parentModified = (Artist) context.getGraphManager().getNode( childModified.getObjectId()); Artist parentCommitted = (Artist) context.getGraphManager().getNode( childCommitted.getObjectId()); Artist parentHollow = (Artist) context.getGraphManager().getNode( childHollow.getObjectId()); assertNotNull(parentNew); assertEquals(PersistenceState.NEW, parentNew.getPersistenceState()); assertEquals("NNN", parentNew.getArtistName()); assertNotNull(parentModified); assertEquals(PersistenceState.MODIFIED, parentModified .getPersistenceState()); assertEquals("MMM", parentModified.getArtistName()); assertNotNull(context.getObjectStore().getChangesByObjectId().get( parentModified.getObjectId())); assertNotNull(parentCommitted); assertEquals(PersistenceState.COMMITTED, parentCommitted .getPersistenceState()); assertNotNull(parentHollow); // TODO: we can assert that when we figure out how nested "invalidate" // should // work // assertEquals(PersistenceState.HOLLOW, // parentHollow.getPersistenceState()); } }); } @Test public void testCommitChangesToParentDeleted() throws Exception { createArtistsDataSet(); DataContext context = createDataContext(); ObjectContext childContext = runtime.newContext(context); // make sure we fetch in predictable order SelectQuery query = new SelectQuery(Artist.class); query.addOrdering(Artist.ARTIST_NAME.asc()); List<?> objects = childContext.performQuery(query); assertEquals(4, objects.size()); // delete AND modify Artist childDeleted = (Artist) objects.get(2); childContext.deleteObjects(childDeleted); childDeleted.setArtistName("DDD"); // don't block queries - on delete Cayenne may need to resolve delete rules via // fetch childContext.commitChangesToParent(); // * all modified child objects must be in committed state now // * all modifications should be propagated to the parent // * no actual commit should occur. assertEquals(PersistenceState.TRANSIENT, childDeleted.getPersistenceState()); Artist parentDeleted = (Artist) context.getGraphManager().getNode( childDeleted.getObjectId()); assertNotNull(parentDeleted); assertEquals(PersistenceState.DELETED, parentDeleted.getPersistenceState()); assertEquals("DDD", parentDeleted.getArtistName()); } @Test public void testCommitChanges() throws Exception { createArtistsDataSet(); DataContext context = createDataContext(); ObjectContext childContext = runtime.newContext(context); // make sure we fetch in predictable order SelectQuery query = new SelectQuery(Artist.class); query.addOrdering(Artist.ARTIST_NAME.asc()); List<?> objects = childContext.performQuery(query); assertEquals(4, objects.size()); Artist childNew = childContext.newObject(Artist.class); childNew.setArtistName("NNN"); Artist childModified = (Artist) objects.get(0); childModified.setArtistName("MMM"); Artist childCommitted = (Artist) objects.get(1); // delete AND modify Artist childDeleted = (Artist) objects.get(2); childContext.deleteObjects(childDeleted); childDeleted.setArtistName("DDD"); Artist childHollow = (Artist) objects.get(3); childContext.invalidateObjects(childHollow); childContext.commitChanges(); assertEquals(PersistenceState.COMMITTED, childNew.getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childModified.getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childCommitted.getPersistenceState()); assertEquals(PersistenceState.TRANSIENT, childDeleted.getPersistenceState()); assertEquals(PersistenceState.HOLLOW, childHollow.getPersistenceState()); Artist parentNew = (Artist) context.getGraphManager().getNode( childNew.getObjectId()); Artist parentModified = (Artist) context.getGraphManager().getNode( childModified.getObjectId()); Artist parentCommitted = (Artist) context.getGraphManager().getNode( childCommitted.getObjectId()); Artist parentDeleted = (Artist) context.getGraphManager().getNode( childDeleted.getObjectId()); Artist parentHollow = (Artist) context.getGraphManager().getNode( childHollow.getObjectId()); assertNotNull(parentNew); assertEquals(PersistenceState.COMMITTED, parentNew.getPersistenceState()); assertEquals("NNN", parentNew.getArtistName()); assertNotNull(parentModified); assertEquals(PersistenceState.COMMITTED, parentModified.getPersistenceState()); assertEquals("MMM", parentModified.getArtistName()); assertNull(context.getObjectStore().getChangesByObjectId().get( parentModified.getObjectId())); assertNull("Deleted object should not be registered.", parentDeleted); assertNotNull(parentCommitted); assertEquals(PersistenceState.COMMITTED, parentCommitted.getPersistenceState()); assertNotNull(parentHollow); } @Test public void testCommitChangesToParent_MergeProperties() throws Exception { createMixedDataSet(); final DataContext context = createDataContext(); final ObjectContext childContext = runtime.newContext(context); // make sure we fetch in predictable order SelectQuery query = new SelectQuery(Painting.class); query.addOrdering(Painting.PAINTING_TITLE.asc()); List<?> objects = childContext.performQuery(query); assertEquals(6, objects.size()); final Painting childModifiedSimple = (Painting) objects.get(0); childModifiedSimple.setPaintingTitle("C_PT"); final Painting childModifiedToOne = (Painting) objects.get(1); childModifiedToOne.setToArtist(childModifiedSimple.getToArtist()); final Artist childModifiedToMany = ((Painting) objects.get(2)).getToArtist(); // ensure painting array is fully resolved... childModifiedToMany.getPaintingArray().size(); childModifiedToMany.addToPaintingArray((Painting) objects.get(3)); queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() { public void execute() { Painting parentModifiedSimple = null; Artist parentModifiedToMany = null; childContext.commitChangesToParent(); assertEquals(PersistenceState.COMMITTED, childModifiedSimple .getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childModifiedToOne .getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childModifiedToMany .getPersistenceState()); parentModifiedSimple = (Painting) context.getGraphManager().getNode( childModifiedSimple.getObjectId()); Painting parentModifiedToOne = (Painting) context .getGraphManager() .getNode(childModifiedToOne.getObjectId()); parentModifiedToMany = (Artist) context.getGraphManager().getNode( childModifiedToMany.getObjectId()); assertNotNull(parentModifiedSimple); assertEquals(PersistenceState.MODIFIED, parentModifiedSimple .getPersistenceState()); assertEquals("C_PT", parentModifiedSimple.getPaintingTitle()); assertNotNull(context.getObjectStore().getChangesByObjectId().get( parentModifiedSimple.getObjectId())); assertNotNull(parentModifiedToOne); assertEquals(PersistenceState.MODIFIED, parentModifiedToOne .getPersistenceState()); assertNotNull(parentModifiedToOne.getToArtist()); assertEquals(33001, Cayenne.intPKForObject(parentModifiedToOne .getToArtist())); assertNotNull(context.getObjectStore().getChangesByObjectId().get( parentModifiedToOne.getObjectId())); // indirectly modified.... assertNotNull(parentModifiedToMany); assertEquals(PersistenceState.MODIFIED, parentModifiedToMany .getPersistenceState()); // here query is expected, as the parent was hollow and its to-many // relationship // is unresolved List<?> paintings = parentModifiedToMany.getPaintingArray(); assertEquals(2, paintings.size()); } }); } @Test public void testCommitChangesToParentPropagatedKey() throws Exception { final DataContext context = createDataContext(); final ObjectContext childContext = runtime.newContext(context); final Painting childMaster = childContext.newObject(Painting.class); childMaster.setPaintingTitle("Master"); final PaintingInfo childDetail1 = childContext.newObject(PaintingInfo.class); childDetail1.setTextReview("Detail1"); childDetail1.setPainting(childMaster); queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() { public void execute() { childContext.commitChangesToParent(); assertEquals(PersistenceState.COMMITTED, childMaster .getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childDetail1 .getPersistenceState()); Painting parentMaster = (Painting) context.getGraphManager().getNode( childMaster.getObjectId()); assertNotNull(parentMaster); assertEquals(PersistenceState.NEW, parentMaster.getPersistenceState()); PaintingInfo parentDetail1 = (PaintingInfo) context .getGraphManager() .getNode(childDetail1.getObjectId()); assertNotNull(parentDetail1); assertEquals(PersistenceState.NEW, parentDetail1.getPersistenceState()); assertSame(parentMaster, parentDetail1.getPainting()); assertSame(parentDetail1, parentMaster.getToPaintingInfo()); } }); } @Test public void testCommitChangesToParentFlattened() throws Exception { final DataContext context = createDataContext(); final ObjectContext childContext = runtime.newContext(context); final Artist childO1 = childContext.newObject(Artist.class); childO1.setArtistName("Master"); final ArtGroup childO2 = childContext.newObject(ArtGroup.class); childO2.setName("Detail1"); childO2.addToArtistArray(childO1); ObjEntity ent = childContext.getEntityResolver().getObjEntity("ArtGroup"); Collection<ObjRelationship> rels = ent.getDeclaredRelationships(); for (ObjRelationship rel : rels) { System.out.println(rel.getName()); } assertEquals(1, childO1.getGroupArray().size()); assertEquals(1, childO2.getArtistArray().size()); queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() { public void execute() { childContext.commitChangesToParent(); assertEquals(PersistenceState.COMMITTED, childO1.getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childO2.getPersistenceState()); Artist parentO1 = (Artist) context.getGraphManager().getNode( childO1.getObjectId()); assertNotNull(parentO1); assertEquals(PersistenceState.NEW, parentO1.getPersistenceState()); ArtGroup parentO2 = (ArtGroup) context.getGraphManager().getNode( childO2.getObjectId()); assertNotNull(parentO2); assertEquals(PersistenceState.NEW, parentO2.getPersistenceState()); assertEquals(1, parentO1.getGroupArray().size()); assertEquals(1, parentO2.getArtistArray().size()); assertTrue(parentO2.getArtistArray().contains(parentO1)); assertTrue(parentO1.getGroupArray().contains(parentO2)); } }); } @Test public void testCommitChangesToParentFlattenedMultipleFlush() throws Exception { final DataContext context = createDataContext(); final ObjectContext childContext = runtime.newContext(context); final Artist childO1 = childContext.newObject(Artist.class); childO1.setArtistName("o1"); final ArtGroup childO2 = childContext.newObject(ArtGroup.class); childO2.setName("o2"); childO2.addToArtistArray(childO1); childContext.commitChangesToParent(); final ArtGroup childO3 = childContext.newObject(ArtGroup.class); childO3.setName("o3"); childO1.addToGroupArray(childO3); assertEquals(2, childO1.getGroupArray().size()); assertEquals(1, childO2.getArtistArray().size()); assertEquals(1, childO3.getArtistArray().size()); queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() { public void execute() { childContext.commitChangesToParent(); assertEquals(PersistenceState.COMMITTED, childO1.getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childO2.getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childO3.getPersistenceState()); Artist parentO1 = (Artist) context.getGraphManager().getNode( childO1.getObjectId()); assertNotNull(parentO1); assertEquals(PersistenceState.NEW, parentO1.getPersistenceState()); ArtGroup parentO2 = (ArtGroup) context.getGraphManager().getNode( childO2.getObjectId()); assertNotNull(parentO2); assertEquals(PersistenceState.NEW, parentO2.getPersistenceState()); ArtGroup parentO3 = (ArtGroup) context.getGraphManager().getNode( childO3.getObjectId()); assertNotNull(parentO3); assertEquals(PersistenceState.NEW, parentO3.getPersistenceState()); assertEquals(2, parentO1.getGroupArray().size()); assertEquals(1, parentO2.getArtistArray().size()); assertEquals(1, parentO3.getArtistArray().size()); assertTrue(parentO2.getArtistArray().contains(parentO1)); assertTrue(parentO3.getArtistArray().contains(parentO1)); assertTrue(parentO1.getGroupArray().contains(parentO2)); assertTrue(parentO1.getGroupArray().contains(parentO3)); } }); childO1.removeFromGroupArray(childO2); queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() { public void execute() { childContext.commitChangesToParent(); assertEquals(PersistenceState.COMMITTED, childO1.getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childO2.getPersistenceState()); assertEquals(PersistenceState.COMMITTED, childO3.getPersistenceState()); Artist parentO1 = (Artist) context.getGraphManager().getNode( childO1.getObjectId()); assertNotNull(parentO1); assertEquals(PersistenceState.NEW, parentO1.getPersistenceState()); ArtGroup parentO2 = (ArtGroup) context.getGraphManager().getNode( childO2.getObjectId()); assertNotNull(parentO2); assertEquals(PersistenceState.NEW, parentO2.getPersistenceState()); ArtGroup parentO3 = (ArtGroup) context.getGraphManager().getNode( childO3.getObjectId()); assertNotNull(parentO3); assertEquals(PersistenceState.NEW, parentO3.getPersistenceState()); assertEquals(1, parentO1.getGroupArray().size()); assertEquals(0, parentO2.getArtistArray().size()); assertEquals(1, parentO3.getArtistArray().size()); assertTrue(parentO3.getArtistArray().contains(parentO1)); assertTrue(parentO1.getGroupArray().contains(parentO3)); } }); } @Test public void testAddRemove() { DataContext context = createDataContext(); ObjectContext child = runtime.newContext(context); Artist a = child.newObject(Artist.class); a.setArtistName("X"); child.commitChanges(); Painting p1 = child.newObject(Painting.class); p1.setPaintingTitle("P1"); a.addToPaintingArray(p1); Painting p2 = child.newObject(Painting.class); p2.setPaintingTitle("P2"); a.addToPaintingArray(p2); a.removeFromPaintingArray(p2); // this causes an error on commit child.deleteObjects(p2); child.commitChangesToParent(); } @Test public void testCAY1194() throws Exception { DataContext context = createDataContext(); Artist artist = context.newObject(Artist.class); artist.setArtistName("111"); ObjectContext child = runtime.newContext(context); Painting painting = child.newObject(Painting.class); painting.setPaintingTitle("222"); Artist localParentMt = child.localObject(artist); assertEquals(0, artist.getPaintingArray().size()); assertEquals(0, localParentMt.getPaintingArray().size()); painting.setToArtist(localParentMt); assertEquals(0, artist.getPaintingArray().size()); assertEquals(1, localParentMt.getPaintingArray().size()); assertEquals(localParentMt.getPaintingArray().get(0).getObjectContext(), child); child.commitChangesToParent(); assertEquals(1, artist.getPaintingArray().size()); assertEquals(artist.getPaintingArray().get(0).getObjectContext(), context); } }