/******************************************************************************* * Copyright (c) 2005, 2015 SAP. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * SAP - initial API and implementation ******************************************************************************/ package org.eclipse.persistence.testing.tests.wdf.jpa1.entitymanager; import java.util.Set; import javax.persistence.EntityManager; import org.eclipse.persistence.testing.framework.wdf.JPAEnvironment; import org.eclipse.persistence.testing.framework.wdf.ToBeInvestigated; import org.eclipse.persistence.testing.models.wdf.jpa1.node.CascadingNode; import org.eclipse.persistence.testing.models.wdf.jpa1.node.CascadingNodeDescription; import org.eclipse.persistence.testing.tests.wdf.jpa1.JPA1Base; import org.junit.Test; public class TestCascadeMerge extends JPA1Base { @Test @ToBeInvestigated public void testCascadeNew() { /* * If X is a new entity instance, a new managed entity instance X' is created and the state of X is copied into the new * managed entity instance X'. * * For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, * Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed * then X is the same object as X'.) */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { CascadingNode parent = new CascadingNode(1, null); CascadingNodeDescription parentDescription = new CascadingNodeDescription(2, null, "new parent"); parent.setDescription(parentDescription); CascadingNode child = new CascadingNode(3, null); CascadingNodeDescription childDescription = new CascadingNodeDescription(4, null, "new child"); child.setDescription(childDescription); CascadingNode grandchild = new CascadingNode(5, null); CascadingNodeDescription grandchildDescription = new CascadingNodeDescription(6, null, "new grandchild"); grandchild.setDescription(grandchildDescription); parent.addChild(child); child.addChild(grandchild); env.beginTransaction(em); CascadingNode mergedNode = em.merge(parent); verify(mergedNode.getId() == parent.getId(), "merged entity has wrong id: expected " + parent.getId() + ", got " + mergedNode.getId()); verify(mergedNode != parent, "merged entity not a copy"); verify(em.contains(mergedNode), "merged entity not managed"); CascadingNodeDescription mergedDescription = mergedNode.getDescription(); verify(mergedDescription.getId() == parentDescription.getId(), "merged entity has wrong id: expected " + parentDescription.getId() + ", got " + mergedDescription.getId()); verify(mergedDescription != parentDescription, "merged entity not a copy"); verify(em.contains(mergedDescription), "merged entity not managed"); Set<CascadingNode> mergedChildren = mergedNode.getChildren(); verify(mergedChildren.size() == 1, "Wrong number of children: expected 1, got " + mergedChildren.size()); for (CascadingNode temp : mergedChildren) { mergedNode = temp; break; } verify(mergedNode.getId() == child.getId(), "merged entity has wrong id: expected " + child.getId() + ", got " + mergedNode.getId()); verify(mergedNode != child, "merged entity not a copy"); verify(em.contains(mergedNode), "merged entity not managed"); mergedDescription = mergedNode.getDescription(); verify(mergedDescription.getId() == childDescription.getId(), "merged entity has wrong id: expected " + childDescription.getId() + ", got " + mergedDescription.getId()); verify(mergedDescription != childDescription, "merged entity not a copy"); verify(em.contains(mergedDescription), "merged entity not managed"); mergedChildren = mergedNode.getChildren(); verify(mergedChildren.size() == 1, "Wrong number of children: expected 1, got " + mergedChildren.size()); for (CascadingNode temp : mergedChildren) { mergedNode = temp; break; } verify(mergedNode.getId() == grandchild.getId(), "merged entity has wrong id: expected " + grandchild.getId() + ", got " + mergedNode.getId()); verify(mergedNode != grandchild, "merged entity not a copy"); verify(em.contains(mergedNode), "merged entity not managed"); mergedDescription = mergedNode.getDescription(); verify(mergedDescription.getId() == grandchildDescription.getId(), "merged entity has wrong id: expected " + grandchildDescription.getId() + ", got " + mergedDescription.getId()); verify(mergedDescription != grandchildDescription, "merged entity not a copy"); verify(em.contains(mergedDescription), "merged entity not managed"); mergedChildren = mergedNode.getChildren(); verify(mergedChildren == null, "merged entity has children"); env.commitTransactionAndClear(em); verifyExistence(em, parent); verifyExistence(em, parentDescription); verifyExistence(em, child); verifyExistence(em, childDescription); verifyExistence(em, grandchild); verifyExistence(em, grandchildDescription); } finally { closeEntityManager(em); } } @Test @ToBeInvestigated public void testCascadeDetached() { /* * If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same * identity or a new managed copy X' of X is created. * * For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, * Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed * then X is the same object as X'.) */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // types of being detached: // parent, parentDescription: not in persistence context but on db // child, childDescription: different object with same pk in persistence context, state FOR_UPDATE // grandchild, grandchildDescription: different object with same pk in persistence context, state FOR_INSERT CascadingNode parent = new CascadingNode(101, null); CascadingNodeDescription parentDescription = new CascadingNodeDescription(102, null, "new parent"); parent.setDescription(parentDescription); CascadingNode child = new CascadingNode(103, null); CascadingNodeDescription childDescription = new CascadingNodeDescription(104, null, "new child"); child.setDescription(childDescription); parent.addChild(child); env.beginTransaction(em); em.persist(parent); env.commitTransactionAndClear(em); verifyExistence(em, parent); verifyExistence(em, parentDescription); verifyExistence(em, child); verifyExistence(em, childDescription); env.beginTransaction(em); CascadingNode grandchild = new CascadingNode(105, null); CascadingNodeDescription grandchildDescription = new CascadingNodeDescription(106, null, "new grandchild"); grandchild.setDescription(grandchildDescription); child.addChild(grandchild); CascadingNode managedChild = em.find(CascadingNode.class, new Integer(child.getId())); CascadingNodeDescription managedChildDescription = em.find(CascadingNodeDescription.class, new Integer( childDescription.getId())); CascadingNode managedGrandchild = new CascadingNode(grandchild.getId(), null); CascadingNodeDescription managedGrandchildDescription = new CascadingNodeDescription(grandchildDescription.getId(), null, "new grandchild"); managedGrandchild.setDescription(managedGrandchildDescription); managedChild.addChild(managedGrandchild); em.persist(managedGrandchild); verify(!em.contains(parent), "parent is managed"); verify(!em.contains(parentDescription), "parentDescription is managed"); verify(!em.contains(child), "child is managed"); verify(!em.contains(childDescription), "childDescription is managed"); verify(!em.contains(grandchild), "grandchild is managed"); verify(!em.contains(grandchildDescription), "grandchildDescription is managed"); verify(em.contains(managedChild), "managedChild is not managed"); verify(em.contains(managedChildDescription), "managedChildDescription is not managed"); verify(em.contains(managedGrandchild), "managedGrandchild is not managed"); verify(em.contains(managedGrandchildDescription), "managedGrandchildDescription is not managed"); // setup complete CascadingNode mergedNode = em.merge(parent); verify(mergedNode.getId() == parent.getId(), "merged entity has wrong id: expected " + parent.getId() + ", got " + mergedNode.getId()); verify(mergedNode != parent, "merged entity not a copy"); verify(em.contains(mergedNode), "merged entity not managed"); CascadingNodeDescription mergedDescription = mergedNode.getDescription(); verify(mergedDescription.getId() == parentDescription.getId(), "merged entity has wrong id: expected " + parentDescription.getId() + ", got " + mergedDescription.getId()); verify(mergedDescription != parentDescription, "merged entity not a copy"); verify(em.contains(mergedDescription), "merged entity not managed"); Set<CascadingNode> mergedChildren = mergedNode.getChildren(); verify(mergedChildren.size() == 1, "Wrong number of children: expected 1, got " + mergedChildren.size()); for (CascadingNode temp : mergedChildren) { mergedNode = temp; break; } verify(mergedNode.getId() == child.getId(), "merged entity has wrong id: expected " + child.getId() + ", got " + mergedNode.getId()); verify(mergedNode != child, "merged entity not a copy"); verify(em.contains(mergedNode), "merged entity not managed"); mergedDescription = mergedNode.getDescription(); verify(mergedDescription.getId() == childDescription.getId(), "merged entity has wrong id: expected " + childDescription.getId() + ", got " + mergedDescription.getId()); verify(mergedDescription != childDescription, "merged entity not a copy"); verify(em.contains(mergedDescription), "merged entity not managed"); mergedChildren = mergedNode.getChildren(); verify(mergedChildren.size() == 1, "Wrong number of children: expected 1, got " + mergedChildren.size()); for (CascadingNode temp : mergedChildren) { mergedNode = temp; break; } verify(mergedNode.getId() == grandchild.getId(), "merged entity has wrong id: expected " + grandchild.getId() + ", got " + mergedNode.getId()); verify(mergedNode != grandchild, "merged entity not a copy"); verify(em.contains(mergedNode), "merged entity not managed"); mergedDescription = mergedNode.getDescription(); verify(mergedDescription.getId() == grandchildDescription.getId(), "merged entity has wrong id: expected " + grandchildDescription.getId() + ", got " + mergedDescription.getId()); verify(mergedDescription != grandchildDescription, "merged entity not a copy"); verify(em.contains(mergedDescription), "merged entity not managed"); mergedChildren = mergedNode.getChildren(); verify(mergedChildren == null, "merged entity has children"); env.commitTransactionAndClear(em); verifyExistence(em, parent); verifyExistence(em, parentDescription); verifyExistence(em, child); verifyExistence(em, childDescription); verifyExistence(em, grandchild); verifyExistence(em, grandchildDescription); } finally { closeEntityManager(em); } } /** * Scenario: Merge a detached entity in case an entity with the same primary key but different object identiy exists in the * persistence context <b>in state FOR_DELETE</b>. The specification does not state clearly how to behave in that case. We * decided to throw an IllegalArgumentException. */ @Test public void testCascadeDetachedRemoved() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // child, childDescription: different object with same pk in persistence context, state FOR_DELETE // others: detached (other types) CascadingNode parent = new CascadingNode(201, null); CascadingNodeDescription parentDescription = new CascadingNodeDescription(202, null, "new parent"); parent.setDescription(parentDescription); CascadingNode child = new CascadingNode(203, null); CascadingNodeDescription childDescription = new CascadingNodeDescription(204, null, "new child"); child.setDescription(childDescription); CascadingNode grandchild = new CascadingNode(205, null); CascadingNodeDescription grandchildDescription = new CascadingNodeDescription(206, null, "new grandchild"); grandchild.setDescription(grandchildDescription); parent.addChild(child); child.addChild(grandchild); env.beginTransaction(em); em.persist(parent); env.commitTransactionAndClear(em); verifyExistence(em, parent); verifyExistence(em, parentDescription); verifyExistence(em, child); verifyExistence(em, childDescription); verifyExistence(em, grandchild); verifyExistence(em, grandchildDescription); env.beginTransaction(em); CascadingNode managedParent = em.find(CascadingNode.class, new Integer(parent.getId())); CascadingNodeDescription managedParentDescription = em.find(CascadingNodeDescription.class, new Integer( parentDescription.getId())); CascadingNode managedChild = em.find(CascadingNode.class, new Integer(child.getId())); CascadingNodeDescription managedChildDescription = em.find(CascadingNodeDescription.class, new Integer( childDescription.getId())); CascadingNode managedGrandchild = em.find(CascadingNode.class, new Integer(grandchild.getId())); CascadingNodeDescription managedGrandchildDescription = em.find(CascadingNodeDescription.class, new Integer( grandchildDescription.getId())); managedParent.setChildren(null); em.remove(managedChild); em.persist(managedGrandchild); verify(!em.contains(parent), "parent is managed"); verify(!em.contains(parentDescription), "parentDescription is managed"); verify(!em.contains(child), "child is managed"); verify(!em.contains(childDescription), "childDescription is managed"); verify(!em.contains(grandchild), "grandchild is managed"); verify(!em.contains(grandchildDescription), "grandchildDescription is managed"); verify(em.contains(managedParent), "managedParent is not managed"); verify(em.contains(managedParentDescription), "managedParentDescription is not managed"); verify(!em.contains(managedChild), "managedChild is not in state FOR_DELETE"); verify(!em.contains(managedChildDescription), "managedChildDescription is not in state FOR_DELETE"); verify(em.contains(managedGrandchild), "managedGrandchild is not managed"); verify(em.contains(managedGrandchildDescription), "managedGrandchildDescription is not managed"); // setup complete boolean mergeFailed = false; try { em.merge(parent); } catch (IllegalArgumentException e) { // $JL-EXC$ this is expected behavior mergeFailed = true; } verify(mergeFailed, "Merge did not throw IllegalArgumentException"); verify(em.contains(managedParent), "managedParent is not managed"); verify(em.contains(managedParentDescription), "managedParentDescription is not managed"); verify(managedParent.getDescription() == managedParentDescription, "managedParent has wrong description"); Set<CascadingNode> children = managedParent.getChildren(); verify(children == null || children.size() == 0, "parent has children"); verify(!em.contains(managedChild), "managedChild is not in state FOR_DELETE"); verify(!em.contains(managedChildDescription), "managedChildDescription is not in state FOR_DELETE"); verify(em.contains(managedGrandchild), "managedGrandchild is not managed"); verify(em.contains(managedGrandchildDescription), "managedGrandchildDescription is not managed"); verify(managedGrandchild.getDescription() == managedGrandchildDescription, "managedGrandchild has wrong description"); env.rollbackTransactionAndClear(em); verifyExistence(em, parent); verifyExistence(em, parentDescription); verifyExistence(em, child); verifyExistence(em, childDescription); verifyExistence(em, grandchild); verifyExistence(em, grandchildDescription); } finally { closeEntityManager(em); } } @Test public void testCircularCascade() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { CascadingNode node1 = new CascadingNode(301, null); CascadingNode node2 = new CascadingNode(302, null); env.beginTransaction(em); em.persist(node1); em.persist(node2); env.commitTransactionAndClear(em); verifyExistence(em, node1); verifyExistence(em, node2); node1.addChild(node2); node2.addChild(node1); env.beginTransaction(em); CascadingNode mergedNode1 = em.merge(node1); verify(em.contains(mergedNode1), "mergedNode1 not managed"); Set<CascadingNode> childrenOfNode1 = mergedNode1.getChildren(); verify(childrenOfNode1.size() == 1, "Wrong number of children: expected 1, got " + childrenOfNode1.size()); CascadingNode childOfNode1 = null; for (CascadingNode temp : childrenOfNode1) { childOfNode1 = temp; break; } verify(childOfNode1.getId() == node2.getId(), "childOfNode1 has wrong id: " + childOfNode1.getId()); verify(em.contains(childOfNode1), "childOfNode1 not managed"); Set<CascadingNode> childrenOfNode2 = childOfNode1.getChildren(); verify(childrenOfNode2.size() == 1, "Wrong number of children: expected 1, got " + childrenOfNode2.size()); CascadingNode childOfNode2 = null; for (CascadingNode temp : childrenOfNode2) { childOfNode2 = temp; break; } verify(childOfNode2 == mergedNode1, "mergedNode1 not child of childOfNode1"); env.commitTransactionAndClear(em); } finally { closeEntityManager(em); } } /** * Tests a scenario where two nodes with the same primary key are related to each other, one is managed, the other detached. */ @Test public void testCircularCascadeWithSamePks() { final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { CascadingNode managedNode = new CascadingNode(401, null); CascadingNode detachedNode = new CascadingNode(managedNode.getId(), null); env.beginTransaction(em); em.persist(managedNode); env.commitTransactionAndClear(em); verifyExistence(em, managedNode); env.beginTransaction(em); managedNode = em.find(CascadingNode.class, new Integer(managedNode.getId())); managedNode.addChild(detachedNode); detachedNode.addChild(managedNode); CascadingNode mergedNode = em.merge(detachedNode); verify(em.contains(mergedNode), "mergedNode not managed"); verify(mergedNode == managedNode, "mergedNode not identical to managedNode"); Set<CascadingNode> children = mergedNode.getChildren(); verify(children.size() == 1, "Wrong number of children: expected 1, got " + children.size()); CascadingNode child = null; for (CascadingNode temp : children) { child = temp; break; } verify(child == managedNode, "child is not identical to managedNode"); env.commitTransactionAndClear(em); } finally { closeEntityManager(em); } } @Test public void testCascadeManaged() { /* * If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities * referenced by relationships from X if these relationships have been annotated with the cascade element value * cascade=MERGE or cascade=ALL annotation. * * For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, * Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed * then X is the same object as X'.) */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // states: // parent, parentDescription: managed // child, childDescription: new CascadingNode parent = new CascadingNode(501, null); CascadingNodeDescription parentDescription = new CascadingNodeDescription(502, null, "managed parent"); parent.setDescription(parentDescription); CascadingNode child = new CascadingNode(503, null); CascadingNodeDescription childDescription = new CascadingNodeDescription(504, null, "new child"); child.setDescription(childDescription); env.beginTransaction(em); em.persist(parent); env.commitTransactionAndClear(em); verifyExistence(em, parent); verifyExistence(em, parentDescription); verifyAbsence(em, child); verifyAbsence(em, childDescription); env.beginTransaction(em); parent = em.find(CascadingNode.class, new Integer(parent.getId())); parent.addChild(child); // setup complete CascadingNode mergedNode = em.merge(parent); verify(mergedNode == parent, "merge returned a copy of the managed entity"); verify(em.contains(mergedNode), "merged entity not managed"); CascadingNodeDescription mergedDescription = mergedNode.getDescription(); verify(mergedDescription.getId() == parentDescription.getId(), "merged entity has wrong id: expected " + parentDescription.getId() + ", got " + mergedNode.getId()); verify(mergedDescription != parentDescription, "merged entity not a copy"); verify(em.contains(mergedDescription), "merged entity not managed"); Set<CascadingNode> mergedChildren = mergedNode.getChildren(); verify(mergedChildren.size() == 1, "Wrong number of children: expected 1, got " + mergedChildren.size()); for (CascadingNode temp : mergedChildren) { mergedNode = temp; break; } verify(mergedNode.getId() == child.getId(), "merged entity has wrong id: expected " + child.getId() + ", got " + mergedNode.getId()); verify(mergedNode != child, "merged entity not a copy"); verify(em.contains(mergedNode), "merged entity not managed"); mergedDescription = mergedNode.getDescription(); verify(mergedDescription.getId() == childDescription.getId(), "merged entity has wrong id: expected " + childDescription.getId() + ", got " + mergedNode.getId()); verify(mergedDescription != childDescription, "merged entity not a copy"); verify(em.contains(mergedDescription), "merged entity not managed"); mergedChildren = mergedNode.getChildren(); verify(mergedChildren == null || mergedChildren.size() == 0, "Node has children, expected none"); env.commitTransactionAndClear(em); verifyExistence(em, parent); verifyExistence(em, parentDescription); verifyExistence(em, child); verifyExistence(em, childDescription); } finally { closeEntityManager(em); } } @Test public void testCascadeRemoved() { /* * If X is a removed entity instance, an IllegalArgumentException will be thrown by the merge operation (or the * transaction commit will fail). */ final JPAEnvironment env = getEnvironment(); final EntityManager em = env.getEntityManager(); try { // states: // parent, parentDescription: new // child, childDescription: removed CascadingNode parent = new CascadingNode(601, null); CascadingNodeDescription parentDescription = new CascadingNodeDescription(602, null, "new parent"); parent.setDescription(parentDescription); CascadingNode child = new CascadingNode(603, null); CascadingNodeDescription childDescription = new CascadingNodeDescription(604, null, "removed child"); child.setDescription(childDescription); env.beginTransaction(em); em.persist(child); env.commitTransactionAndClear(em); verifyAbsence(em, parent); verifyAbsence(em, parentDescription); verifyExistence(em, child); verifyExistence(em, childDescription); env.beginTransaction(em); child = em.find(CascadingNode.class, new Integer(child.getId())); em.remove(child); parent.addChild(child); verify(!em.contains(parent), "parent not new"); verify(!em.contains(child), "child not removed"); // setup complete boolean mergeFailed = false; boolean immediateException = false; try { em.merge(parent); } catch (IllegalArgumentException e) { mergeFailed = true; immediateException = true; verify(!em.contains(parent), "parent not new"); verify(!em.contains(child), "child not removed"); } if (!immediateException) { try { env.commitTransactionAndClear(em); } catch (RuntimeException e) { if (!checkForPersistenceException(e)) { throw e; } mergeFailed = true; } } else { env.rollbackTransactionAndClear(em); } verify(mergeFailed, "merge succeeded on a removed entity"); verifyAbsence(em, parent); verifyAbsence(em, parentDescription); verifyExistence(em, child); verifyExistence(em, childDescription); } finally { closeEntityManager(em); } } private void verifyExistence(EntityManager em, Object entity) { if (entity instanceof CascadingNode) { CascadingNode node = (CascadingNode) entity; CascadingNode found = em.find(CascadingNode.class, new Integer(node.getId())); verify(found != null, "cascading node with id " + node.getId() + " not found"); } else if (entity instanceof CascadingNodeDescription) { CascadingNodeDescription desc = (CascadingNodeDescription) entity; CascadingNodeDescription found = em.find(CascadingNodeDescription.class, new Integer(desc.getId())); verify(found != null, "cascading node description with id " + desc.getId() + " not found"); } else { throw new IllegalArgumentException("not supported"); } } private void verifyAbsence(EntityManager em, Object entity) { if (entity instanceof CascadingNode) { CascadingNode node = (CascadingNode) entity; CascadingNode found = em.find(CascadingNode.class, new Integer(node.getId())); verify(found == null, "cascading node with id " + node.getId() + " found"); } else if (entity instanceof CascadingNodeDescription) { CascadingNodeDescription desc = (CascadingNodeDescription) entity; CascadingNodeDescription found = em.find(CascadingNodeDescription.class, new Integer(desc.getId())); verify(found == null, "cascading node description with id " + desc.getId() + " found"); } else { throw new IllegalArgumentException("not supported"); } } }