================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/descriptors/ObjectBuilder.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000001/AB0952363AC40CBFE034080020E8C54E.26 Report generated at Mon Oct 2 16:56:46 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000001/AB0952363AC40CBFE034080020E8C54E.26 Fri Sep 29 11:20:57 2006 --- /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/descriptors/ObjectBuilder.java Mon Oct 2 16:56:46 2006 *************** *** 2052,2061 **** * This merge also prevents the extra step of calculating the changes when it is not required. */ public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager) { // PERF: Avoid synchronized enumerator as is concurrency bottleneck. Vector mappings = getDescriptor().getMappings(); for (int index = 0; index < mappings.size(); index++) { ! ((DatabaseMapping)mappings.get(index)).mergeIntoObject(target, isUnInitialized, source, mergeManager); } // PERF: Avoid events if no listeners. --- 2052,2077 ---- * This merge also prevents the extra step of calculating the changes when it is not required. */ public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager) { + mergeIntoObject(target, isUnInitialized, source, mergeManager, false); + } + + /** + * INTERNAL: + * Merge the contents of one object into another, this merge algorthim is dependent on the merge manager. + * This merge also prevents the extra step of calculating the changes when it is not required. + * If 'cascadeOnly' is true, only foreign reference mappings are merged. + */ + public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager, boolean cascadeOnly) { + // cascadeOnly is introduced to optimize merge + // for GF#1139 Cascade merge operations to relationship mappings even if already registered + // PERF: Avoid synchronized enumerator as is concurrency bottleneck. Vector mappings = getDescriptor().getMappings(); for (int index = 0; index < mappings.size(); index++) { ! DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); ! if(!cascadeOnly || mapping.isForeignReferenceMapping()){ ! mapping.mergeIntoObject(target, isUnInitialized, source, mergeManager); ! } } // PERF: Avoid events if no listeners. ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/ejb/cmp3/base/EntityManagerImpl.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000002/AB0952363AC40CBFE034080020E8C54E.42 Report generated at Mon Oct 2 16:56:46 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000002/AB0952363AC40CBFE034080020E8C54E.42 Fri Sep 29 11:21:13 2006 --- /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/ejb/cmp3/base/EntityManagerImpl.java Mon Oct 2 16:56:46 2006 *************** *** 200,206 **** throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new Object[]{entity})); } try { ! return getActivePersistenceContext(checkForTransaction(!isExtended())).mergeCloneWithReferences(entity, MergeManager.CASCADE_BY_MAPPING); } catch (oracle.toplink.essentials.exceptions.OptimisticLockException ole) { throw new javax.persistence.OptimisticLockException(ole); } --- 200,206 ---- throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new Object[]{entity})); } try { ! return getActivePersistenceContext(checkForTransaction(!isExtended())).mergeCloneWithReferences(entity, MergeManager.CASCADE_BY_MAPPING, true); } catch (oracle.toplink.essentials.exceptions.OptimisticLockException ole) { throw new javax.persistence.OptimisticLockException(ole); } ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/sessions/MergeManager.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000003/AB0952363AC40CBFE034080020E8C54E.12 Report generated at Mon Oct 2 16:56:46 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000003/AB0952363AC40CBFE034080020E8C54E.12 Fri Sep 29 11:21:59 2006 --- /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/sessions/MergeManager.java Mon Oct 2 16:56:46 2006 *************** *** 82,87 **** --- 82,91 ---- protected long systemTime = 0;// stored so that all objects merged by a merge manager can have the same readTime public static boolean LOCK_ON_MERGE = true; + /** Force cascade merge even if a clone is already registered */ + // GF#1139 Cascade doesn't work when merging managed entity + protected boolean forceCascade; + public MergeManager(AbstractSession session) { this.session = session; this.objectsAlreadyMerged = new IdentityHashtable(); *************** *** 387,393 **** ClassDescriptor descriptor = getSession().getDescriptor(rmiClone); Object registeredObject = registerObjectForMergeCloneIntoWorkingCopy(rmiClone); ! if (registeredObject == rmiClone) { //need to find better better fix. prevents merging into itself. return rmiClone; } --- 391,397 ---- ClassDescriptor descriptor = getSession().getDescriptor(rmiClone); Object registeredObject = registerObjectForMergeCloneIntoWorkingCopy(rmiClone); ! if (registeredObject == rmiClone && !shouldForceCascade()) { //need to find better better fix. prevents merging into itself. return rmiClone; } *************** *** 396,402 **** try { ObjectBuilder builder = descriptor.getObjectBuilder(); ! if (descriptor.usesVersionLocking()) { VersionLockingPolicy policy = (VersionLockingPolicy) descriptor.getOptimisticLockingPolicy(); if (policy.isStoredInObject()) { Object currentValue = builder.extractValueFromObjectForField(registeredObject, policy.getWriteLockField(), session); --- 400,406 ---- try { ObjectBuilder builder = descriptor.getObjectBuilder(); ! if (registeredObject != rmiClone && descriptor.usesVersionLocking()) { VersionLockingPolicy policy = (VersionLockingPolicy) descriptor.getOptimisticLockingPolicy(); if (policy.isStoredInObject()) { Object currentValue = builder.extractValueFromObjectForField(registeredObject, policy.getWriteLockField(), session); *************** *** 410,417 **** // Toggle change tracking during the merge. descriptor.getObjectChangePolicy().dissableEventProcessing(registeredObject); // Merge into the clone from the original, use clone as backup as anything different should be merged. ! builder.mergeIntoObject(registeredObject, false, rmiClone, this); } finally { descriptor.getObjectChangePolicy().enableEventProcessing(registeredObject); } --- 414,426 ---- // Toggle change tracking during the merge. descriptor.getObjectChangePolicy().dissableEventProcessing(registeredObject); + boolean cascadeOnly = false; + if(registeredObject == rmiClone){ + // GF#1139 Cascade merge operations to relationship mappings even if already registered + cascadeOnly = true; + } // Merge into the clone from the original, use clone as backup as anything different should be merged. ! builder.mergeIntoObject(registeredObject, false, rmiClone, this, cascadeOnly); } finally { descriptor.getObjectChangePolicy().enableEventProcessing(registeredObject); } *************** *** 693,698 **** --- 702,711 ---- this.mergePolicy = mergePolicy; } + public void setForceCascade(boolean forceCascade) { + this.forceCascade = forceCascade; + } + public void setObjectDescriptors(IdentityHashtable objectDescriptors) { this.objectDescriptors = objectDescriptors; } *************** *** 818,821 **** --- 831,841 ---- public boolean shouldRefreshRemoteObject() { return getMergePolicy() == REFRESH_REMOTE_OBJECT; } + + /** + * This is used to cascade merge even if a clone is already registered. + */ + public boolean shouldForceCascade() { + return forceCascade; + } } ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/sessions/UnitOfWorkImpl.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000004/AB0952363AC40CBFE034080020E8C54E.25 Report generated at Mon Oct 2 16:56:46 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000004/AB0952363AC40CBFE034080020E8C54E.25 Fri Sep 29 11:22:06 2006 --- /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/sessions/UnitOfWorkImpl.java Mon Oct 2 16:56:46 2006 *************** *** 2665,2671 **** * @see #deepMergeClone(Object) */ public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy) { ! if (rmiClone == null) { return null; } --- 2665,2689 ---- * @see #deepMergeClone(Object) */ public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy) { ! return mergeCloneWithReferences(rmiClone, cascadePolicy, false); ! } ! ! /** ! * INTERNAL: ! * Merge the attributes of the clone into the unit of work copy. ! * This can be used for objects that are returned from the client through ! * RMI serialization (or another serialization mechanism), because the RMI object ! * will be a clone this will merge its attributes correctly to preserve object ! * identity within the unit of work and record its changes. ! * ! * The object and its private owned parts are merged. This will include references from ! * dependent objects to independent objects. ! * ! * @return the registered version for the clone being merged. ! * @see #shallowMergeClone(Object) ! * @see #deepMergeClone(Object) ! */ ! public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy, boolean forceCascade) { if (rmiClone == null) { return null; } *************** *** 2686,2691 **** --- 2704,2710 ---- MergeManager manager = new MergeManager(this); manager.mergeCloneWithReferencesIntoWorkingCopy(); manager.setCascadePolicy(cascadePolicy); + manager.setForceCascade(forceCascade); Object mergedObject = manager.mergeChanges(implementation, null); if (isSmartMerge()) { return builder.wrapObject(mergedObject, this); ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Employee.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000005/AB0952363AC40CBFE034080020E8C54E.19 Report generated at Mon Oct 2 16:56:46 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000005/AB0952363AC40CBFE034080020E8C54E.19 Fri Sep 29 11:26:30 2006 --- /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Employee.java Mon Oct 2 16:56:46 2006 *************** *** 205,211 **** this.manager = manager; } ! @ManyToMany(cascade=PERSIST) @JoinTable( name="CMP3_EMP_PROJ", // Default for the project side and specify for the employee side --- 205,211 ---- this.manager = manager; } ! @ManyToMany(cascade={PERSIST, MERGE}) @JoinTable( name="CMP3_EMP_PROJ", // Default for the project side and specify for the employee side ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Project.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000006/AB0952363AC40CBFE034080020E8C54E.6 Report generated at Mon Oct 2 16:56:46 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000006/AB0952363AC40CBFE034080020E8C54E.6 Fri Sep 29 11:30:49 2006 --- /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Project.java Mon Oct 2 16:56:46 2006 *************** *** 111,117 **** this.description = description; } ! @OneToOne @JoinColumn(name="LEADER_ID") public Employee getTeamLeader() { return teamLeader; --- 111,117 ---- this.description = description; } ! @OneToOne(cascade = {CascadeType.MERGE}) @JoinColumn(name="LEADER_ID") public Employee getTeamLeader() { return teamLeader; ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/tests/cmp3/advanced/EntityManagerJUnitTestSuite.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000007/AB0952363AC40CBFE034080020E8C54E.52 Report generated at Mon Oct 2 16:56:46 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000007/AB0952363AC40CBFE034080020E8C54E.52 Mon Oct 2 15:14:43 2006 --- /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/tests/cmp3/advanced/EntityManagerJUnitTestSuite.java Mon Oct 2 16:56:46 2006 *************** *** 2556,2561 **** --- 2556,2697 ---- fail("No exception thrown when primary key update attempted."); } + // Test cascade merge on a detached entity + public void testCascadeMergeDetached() { + // setup + Project p1 = new Project(); + p1.setName("Project1"); + Project p2 = new Project(); + p1.setName("Project2"); + Employee e1 = new Employee(); + e1.setFirstName("Employee1"); + Employee e2 = new Employee(); + e2.setFirstName("Employee2"); + + EntityManager em = createEntityManager(); + em.getTransaction().begin(); + try { + em.persist(p1); + em.persist(p2); + em.persist(e1); + em.persist(e2); + + em.getTransaction().commit(); + } catch (RuntimeException re){ + em.getTransaction().rollback(); + throw re; + } + em.close(); + // end of setup + + //p1,p2,e1,e2 are detached + + // associate relationships + //p1 -> e1 (one-to-one) + p1.setTeamLeader(e1); + //e1 -> e2 (one-to-many) + e1.addManagedEmployee(e2); + //e2 -> p2 (many-to-many) + e2.addProject(p2); + p2.addTeamMember(e2); + + em = createEntityManager(); + em.getTransaction().begin(); + try { + Project mp1 = em.merge(p1); // cascade merge + assertTrue(em.contains(mp1)); + assertTrue("Managed instance and detached instance must not be same", mp1 != p1); + + Employee me1 = mp1.getTeamLeader(); + assertTrue("Cascade merge failed", em.contains(me1)); + assertTrue("Managed instance and detached instance must not be same", me1 != e1); + + Employee me2 = me1.getManagedEmployees().iterator().next(); + assertTrue("Cascade merge failed", em.contains(me2)); + assertTrue("Managed instance and detached instance must not be same", me2 != e2); + + Project mp2 = me2.getProjects().iterator().next(); + assertTrue("Cascade merge failed", em.contains(mp2)); + assertTrue("Managed instance and detached instance must not be same", mp2 != p2); + + em.getTransaction().commit(); + } catch (RuntimeException re){ + em.getTransaction().rollback(); + throw re; + } + em.close(); + } + + // Test cascade merge on a managed entity + // Test for GF#1139 - Cascade doesn't work when merging managed entity + public void testCascadeMergeManaged() { + // setup + Project p1 = new Project(); + p1.setName("Project1"); + Project p2 = new Project(); + p1.setName("Project2"); + Employee e1 = new Employee(); + e1.setFirstName("Employee1"); + Employee e2 = new Employee(); + e2.setFirstName("Employee2"); + + EntityManager em = createEntityManager(); + em.getTransaction().begin(); + try { + em.persist(p1); + em.persist(p2); + em.persist(e1); + em.persist(e2); + + em.getTransaction().commit(); + } catch (RuntimeException re){ + em.getTransaction().rollback(); + throw re; + } + em.close(); + // end of setup + + //p1,p2,e1,e2 are detached + em = createEntityManager(); + em.getTransaction().begin(); + try { + Project mp1 = em.merge(p1); + assertTrue(em.contains(mp1)); + assertTrue("Managed instance and detached instance must not be same", mp1 != p1); + + //p1 -> e1 (one-to-one) + mp1.setTeamLeader(e1); + mp1 = em.merge(mp1); // merge again - trigger cascade merge + + Employee me1 = mp1.getTeamLeader(); + assertTrue("Cascade merge failed", em.contains(me1)); + assertTrue("Managed instance and detached instance must not be same", me1 != e1); + + //e1 -> e2 (one-to-many) + me1.addManagedEmployee(e2); + me1 = em.merge(me1); // merge again - trigger cascade merge + + Employee me2 = me1.getManagedEmployees().iterator().next(); + assertTrue("Cascade merge failed", em.contains(me2)); + assertTrue("Managed instance and detached instance must not be same", me2 != e2); + + //e2 -> p2 (many-to-many) + me2.addProject(p2); + p2.addTeamMember(me2); + me2 = em.merge(me2); // merge again - trigger cascade merge + + Project mp2 = me2.getProjects().iterator().next(); + assertTrue("Cascade merge failed", em.contains(mp2)); + assertTrue("Managed instance and detached instance must not be same", mp2 != p2); + + em.getTransaction().commit(); + } catch (RuntimeException re){ + em.getTransaction().rollback(); + throw re; + } + em.close(); + } + public static void main(String[] args) { // Now run JUnit. junit.swingui.TestRunner.main(args); ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/descriptors/ObjectBuilder.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000001/AB0952363AC40CBFE034080020E8C54E.26 Report generated at Tue Oct 3 09:04:35 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000001/AB0952363AC40CBFE034080020E8C54E.26 Fri Sep 29 11:20:57 2006 --- /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/descriptors/ObjectBuilder.java Mon Oct 2 16:56:46 2006 *************** *** 2052,2061 **** * This merge also prevents the extra step of calculating the changes when it is not required. */ public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager) { // PERF: Avoid synchronized enumerator as is concurrency bottleneck. Vector mappings = getDescriptor().getMappings(); for (int index = 0; index < mappings.size(); index++) { ! ((DatabaseMapping)mappings.get(index)).mergeIntoObject(target, isUnInitialized, source, mergeManager); } // PERF: Avoid events if no listeners. --- 2052,2077 ---- * This merge also prevents the extra step of calculating the changes when it is not required. */ public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager) { + mergeIntoObject(target, isUnInitialized, source, mergeManager, false); + } + + /** + * INTERNAL: + * Merge the contents of one object into another, this merge algorthim is dependent on the merge manager. + * This merge also prevents the extra step of calculating the changes when it is not required. + * If 'cascadeOnly' is true, only foreign reference mappings are merged. + */ + public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager, boolean cascadeOnly) { + // cascadeOnly is introduced to optimize merge + // for GF#1139 Cascade merge operations to relationship mappings even if already registered + // PERF: Avoid synchronized enumerator as is concurrency bottleneck. Vector mappings = getDescriptor().getMappings(); for (int index = 0; index < mappings.size(); index++) { ! DatabaseMapping mapping = (DatabaseMapping)mappings.get(index); ! if(!cascadeOnly || mapping.isForeignReferenceMapping()){ ! mapping.mergeIntoObject(target, isUnInitialized, source, mergeManager); ! } } // PERF: Avoid events if no listeners. ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/ejb/cmp3/base/EntityManagerImpl.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000002/AB0952363AC40CBFE034080020E8C54E.42 Report generated at Tue Oct 3 09:04:35 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000002/AB0952363AC40CBFE034080020E8C54E.42 Fri Sep 29 11:21:13 2006 --- /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/ejb/cmp3/base/EntityManagerImpl.java Mon Oct 2 16:56:46 2006 *************** *** 200,206 **** throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new Object[]{entity})); } try { ! return getActivePersistenceContext(checkForTransaction(!isExtended())).mergeCloneWithReferences(entity, MergeManager.CASCADE_BY_MAPPING); } catch (oracle.toplink.essentials.exceptions.OptimisticLockException ole) { throw new javax.persistence.OptimisticLockException(ole); } --- 200,206 ---- throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new Object[]{entity})); } try { ! return getActivePersistenceContext(checkForTransaction(!isExtended())).mergeCloneWithReferences(entity, MergeManager.CASCADE_BY_MAPPING, true); } catch (oracle.toplink.essentials.exceptions.OptimisticLockException ole) { throw new javax.persistence.OptimisticLockException(ole); } ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/sessions/MergeManager.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000003/AB0952363AC40CBFE034080020E8C54E.12 Report generated at Tue Oct 3 09:04:35 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000003/AB0952363AC40CBFE034080020E8C54E.12 Fri Sep 29 11:21:59 2006 --- /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/sessions/MergeManager.java Mon Oct 2 16:56:46 2006 *************** *** 82,87 **** --- 82,91 ---- protected long systemTime = 0;// stored so that all objects merged by a merge manager can have the same readTime public static boolean LOCK_ON_MERGE = true; + /** Force cascade merge even if a clone is already registered */ + // GF#1139 Cascade doesn't work when merging managed entity + protected boolean forceCascade; + public MergeManager(AbstractSession session) { this.session = session; this.objectsAlreadyMerged = new IdentityHashtable(); *************** *** 387,393 **** ClassDescriptor descriptor = getSession().getDescriptor(rmiClone); Object registeredObject = registerObjectForMergeCloneIntoWorkingCopy(rmiClone); ! if (registeredObject == rmiClone) { //need to find better better fix. prevents merging into itself. return rmiClone; } --- 391,397 ---- ClassDescriptor descriptor = getSession().getDescriptor(rmiClone); Object registeredObject = registerObjectForMergeCloneIntoWorkingCopy(rmiClone); ! if (registeredObject == rmiClone && !shouldForceCascade()) { //need to find better better fix. prevents merging into itself. return rmiClone; } *************** *** 396,402 **** try { ObjectBuilder builder = descriptor.getObjectBuilder(); ! if (descriptor.usesVersionLocking()) { VersionLockingPolicy policy = (VersionLockingPolicy) descriptor.getOptimisticLockingPolicy(); if (policy.isStoredInObject()) { Object currentValue = builder.extractValueFromObjectForField(registeredObject, policy.getWriteLockField(), session); --- 400,406 ---- try { ObjectBuilder builder = descriptor.getObjectBuilder(); ! if (registeredObject != rmiClone && descriptor.usesVersionLocking()) { VersionLockingPolicy policy = (VersionLockingPolicy) descriptor.getOptimisticLockingPolicy(); if (policy.isStoredInObject()) { Object currentValue = builder.extractValueFromObjectForField(registeredObject, policy.getWriteLockField(), session); *************** *** 410,417 **** // Toggle change tracking during the merge. descriptor.getObjectChangePolicy().dissableEventProcessing(registeredObject); // Merge into the clone from the original, use clone as backup as anything different should be merged. ! builder.mergeIntoObject(registeredObject, false, rmiClone, this); } finally { descriptor.getObjectChangePolicy().enableEventProcessing(registeredObject); } --- 414,426 ---- // Toggle change tracking during the merge. descriptor.getObjectChangePolicy().dissableEventProcessing(registeredObject); + boolean cascadeOnly = false; + if(registeredObject == rmiClone){ + // GF#1139 Cascade merge operations to relationship mappings even if already registered + cascadeOnly = true; + } // Merge into the clone from the original, use clone as backup as anything different should be merged. ! builder.mergeIntoObject(registeredObject, false, rmiClone, this, cascadeOnly); } finally { descriptor.getObjectChangePolicy().enableEventProcessing(registeredObject); } *************** *** 693,698 **** --- 702,711 ---- this.mergePolicy = mergePolicy; } + public void setForceCascade(boolean forceCascade) { + this.forceCascade = forceCascade; + } + public void setObjectDescriptors(IdentityHashtable objectDescriptors) { this.objectDescriptors = objectDescriptors; } *************** *** 818,821 **** --- 831,841 ---- public boolean shouldRefreshRemoteObject() { return getMergePolicy() == REFRESH_REMOTE_OBJECT; } + + /** + * This is used to cascade merge even if a clone is already registered. + */ + public boolean shouldForceCascade() { + return forceCascade; + } } ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/sessions/UnitOfWorkImpl.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000004/AB0952363AC40CBFE034080020E8C54E.25 Report generated at Tue Oct 3 09:04:35 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000004/AB0952363AC40CBFE034080020E8C54E.25 Fri Sep 29 11:22:06 2006 --- /ade/gyorke_ref-essentials/tldev/source/essentials/oracle/toplink/essentials/internal/sessions/UnitOfWorkImpl.java Mon Oct 2 16:56:46 2006 *************** *** 2665,2671 **** * @see #deepMergeClone(Object) */ public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy) { ! if (rmiClone == null) { return null; } --- 2665,2689 ---- * @see #deepMergeClone(Object) */ public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy) { ! return mergeCloneWithReferences(rmiClone, cascadePolicy, false); ! } ! ! /** ! * INTERNAL: ! * Merge the attributes of the clone into the unit of work copy. ! * This can be used for objects that are returned from the client through ! * RMI serialization (or another serialization mechanism), because the RMI object ! * will be a clone this will merge its attributes correctly to preserve object ! * identity within the unit of work and record its changes. ! * ! * The object and its private owned parts are merged. This will include references from ! * dependent objects to independent objects. ! * ! * @return the registered version for the clone being merged. ! * @see #shallowMergeClone(Object) ! * @see #deepMergeClone(Object) ! */ ! public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy, boolean forceCascade) { if (rmiClone == null) { return null; } *************** *** 2686,2691 **** --- 2704,2710 ---- MergeManager manager = new MergeManager(this); manager.mergeCloneWithReferencesIntoWorkingCopy(); manager.setCascadePolicy(cascadePolicy); + manager.setForceCascade(forceCascade); Object mergedObject = manager.mergeChanges(implementation, null); if (isSmartMerge()) { return builder.wrapObject(mergedObject, this); ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Employee.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000005/AB0952363AC40CBFE034080020E8C54E.19 Report generated at Tue Oct 3 09:04:35 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000005/AB0952363AC40CBFE034080020E8C54E.19 Fri Sep 29 11:26:30 2006 --- /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Employee.java Mon Oct 2 16:56:46 2006 *************** *** 205,211 **** this.manager = manager; } ! @ManyToMany(cascade=PERSIST) @JoinTable( name="CMP3_EMP_PROJ", // Default for the project side and specify for the employee side --- 205,211 ---- this.manager = manager; } ! @ManyToMany(cascade={PERSIST, MERGE}) @JoinTable( name="CMP3_EMP_PROJ", // Default for the project side and specify for the employee side ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Project.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000006/AB0952363AC40CBFE034080020E8C54E.6 Report generated at Tue Oct 3 09:04:35 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000006/AB0952363AC40CBFE034080020E8C54E.6 Fri Sep 29 11:30:49 2006 --- /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Project.java Mon Oct 2 16:56:46 2006 *************** *** 111,117 **** this.description = description; } ! @OneToOne @JoinColumn(name="LEADER_ID") public Employee getTeamLeader() { return teamLeader; --- 111,117 ---- this.description = description; } ! @OneToOne(cascade = {CascadeType.MERGE}) @JoinColumn(name="LEADER_ID") public Employee getTeamLeader() { return teamLeader; ================================================================================ Merge Diffs: /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/tests/cmp3/advanced/EntityManagerJUnitTestSuite.java vs. /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000007/AB0952363AC40CBFE034080020E8C54E.52 Report generated at Tue Oct 3 09:04:35 2006 -------------------------------------------------------------------------------- *** /net/stottnfs2.ca.oracle.com/vol/vol1/ade_ottawa_txn/gyorke/gyorke_gfissue1139-cascade-merge-on-managed-entity_060929/ade_storage/000007/AB0952363AC40CBFE034080020E8C54E.52 Mon Oct 2 15:14:43 2006 --- /ade/gyorke_ref-essentials/tltest/source/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/tests/cmp3/advanced/EntityManagerJUnitTestSuite.java Mon Oct 2 16:56:46 2006 *************** *** 2556,2561 **** --- 2556,2697 ---- fail("No exception thrown when primary key update attempted."); } + // Test cascade merge on a detached entity + public void testCascadeMergeDetached() { + // setup + Project p1 = new Project(); + p1.setName("Project1"); + Project p2 = new Project(); + p1.setName("Project2"); + Employee e1 = new Employee(); + e1.setFirstName("Employee1"); + Employee e2 = new Employee(); + e2.setFirstName("Employee2"); + + EntityManager em = createEntityManager(); + em.getTransaction().begin(); + try { + em.persist(p1); + em.persist(p2); + em.persist(e1); + em.persist(e2); + + em.getTransaction().commit(); + } catch (RuntimeException re){ + em.getTransaction().rollback(); + throw re; + } + em.close(); + // end of setup + + //p1,p2,e1,e2 are detached + + // associate relationships + //p1 -> e1 (one-to-one) + p1.setTeamLeader(e1); + //e1 -> e2 (one-to-many) + e1.addManagedEmployee(e2); + //e2 -> p2 (many-to-many) + e2.addProject(p2); + p2.addTeamMember(e2); + + em = createEntityManager(); + em.getTransaction().begin(); + try { + Project mp1 = em.merge(p1); // cascade merge + assertTrue(em.contains(mp1)); + assertTrue("Managed instance and detached instance must not be same", mp1 != p1); + + Employee me1 = mp1.getTeamLeader(); + assertTrue("Cascade merge failed", em.contains(me1)); + assertTrue("Managed instance and detached instance must not be same", me1 != e1); + + Employee me2 = me1.getManagedEmployees().iterator().next(); + assertTrue("Cascade merge failed", em.contains(me2)); + assertTrue("Managed instance and detached instance must not be same", me2 != e2); + + Project mp2 = me2.getProjects().iterator().next(); + assertTrue("Cascade merge failed", em.contains(mp2)); + assertTrue("Managed instance and detached instance must not be same", mp2 != p2); + + em.getTransaction().commit(); + } catch (RuntimeException re){ + em.getTransaction().rollback(); + throw re; + } + em.close(); + } + + // Test cascade merge on a managed entity + // Test for GF#1139 - Cascade doesn't work when merging managed entity + public void testCascadeMergeManaged() { + // setup + Project p1 = new Project(); + p1.setName("Project1"); + Project p2 = new Project(); + p1.setName("Project2"); + Employee e1 = new Employee(); + e1.setFirstName("Employee1"); + Employee e2 = new Employee(); + e2.setFirstName("Employee2"); + + EntityManager em = createEntityManager(); + em.getTransaction().begin(); + try { + em.persist(p1); + em.persist(p2); + em.persist(e1); + em.persist(e2); + + em.getTransaction().commit(); + } catch (RuntimeException re){ + em.getTransaction().rollback(); + throw re; + } + em.close(); + // end of setup + + //p1,p2,e1,e2 are detached + em = createEntityManager(); + em.getTransaction().begin(); + try { + Project mp1 = em.merge(p1); + assertTrue(em.contains(mp1)); + assertTrue("Managed instance and detached instance must not be same", mp1 != p1); + + //p1 -> e1 (one-to-one) + mp1.setTeamLeader(e1); + mp1 = em.merge(mp1); // merge again - trigger cascade merge + + Employee me1 = mp1.getTeamLeader(); + assertTrue("Cascade merge failed", em.contains(me1)); + assertTrue("Managed instance and detached instance must not be same", me1 != e1); + + //e1 -> e2 (one-to-many) + me1.addManagedEmployee(e2); + me1 = em.merge(me1); // merge again - trigger cascade merge + + Employee me2 = me1.getManagedEmployees().iterator().next(); + assertTrue("Cascade merge failed", em.contains(me2)); + assertTrue("Managed instance and detached instance must not be same", me2 != e2); + + //e2 -> p2 (many-to-many) + me2.addProject(p2); + p2.addTeamMember(me2); + me2 = em.merge(me2); // merge again - trigger cascade merge + + Project mp2 = me2.getProjects().iterator().next(); + assertTrue("Cascade merge failed", em.contains(mp2)); + assertTrue("Managed instance and detached instance must not be same", mp2 != p2); + + em.getTransaction().commit(); + } catch (RuntimeException re){ + em.getTransaction().rollback(); + throw re; + } + em.close(); + } + public static void main(String[] args) { // Now run JUnit. junit.swingui.TestRunner.main(args);