blob: 80a8efd27e6cb4e60ae27d657df4780ad083d81c [file] [log] [blame]
/*
* Copyright (c) 2005, 2021 Oracle and/or its affiliates. All rights reserved.
* 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// SAP - initial API and implementation
package org.eclipse.persistence.testing.tests.wdf.jpa1.entitymanager;
import java.util.Set;
import jakarta.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, child.getId());
CascadingNodeDescription managedChildDescription = em.find(CascadingNodeDescription.class, 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, parent.getId());
CascadingNodeDescription managedParentDescription = em.find(CascadingNodeDescription.class, parentDescription.getId());
CascadingNode managedChild = em.find(CascadingNode.class, child.getId());
CascadingNodeDescription managedChildDescription = em.find(CascadingNodeDescription.class, childDescription.getId());
CascadingNode managedGrandchild = em.find(CascadingNode.class, grandchild.getId());
CascadingNodeDescription managedGrandchildDescription = em.find(CascadingNodeDescription.class, 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, 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, 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, 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, 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, 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, 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, desc.getId());
verify(found == null, "cascading node description with id " + desc.getId() + " found");
} else {
throw new IllegalArgumentException("not supported");
}
}
}