mirror of https://github.com/apache/openjpa.git
OPENJPA-628: Adding more test cases for testing proxy collections in tracking changes in detached mode.
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@682875 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
e41e1dd976
commit
a7464c7e0a
|
@ -0,0 +1,165 @@
|
|||
package org.apache.openjpa.persistence.proxy;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
|
||||
import org.apache.openjpa.util.ChangeTracker;
|
||||
import org.apache.openjpa.util.ProxyCollection;
|
||||
|
||||
/**
|
||||
* Tests proxying and change tracking of collection fields for modification in
|
||||
* detached state.
|
||||
*
|
||||
* Originally reported in
|
||||
* <A HREF="https://issues.apache.org/jira/browse/OPENJPA-628">OPENJPA-628</A>
|
||||
*
|
||||
* @author Pinaki Poddar
|
||||
*
|
||||
*/
|
||||
public class TestProxyCollection extends SingleEMFTestCase {
|
||||
public void setUp() {
|
||||
super.setUp(CLEAR_TABLES, TreeNode.class);
|
||||
}
|
||||
/**
|
||||
* Tests that a uniform tree is created with expected fan outs at each
|
||||
* level. This is not a persistent operation, just in-memory.
|
||||
*/
|
||||
public void testCreateTree() {
|
||||
TreeNode root = new TreeNode();
|
||||
root.setName("0");
|
||||
int[] fanOuts = {1,2,3};
|
||||
root.createTree(fanOuts);
|
||||
assertArrayEquals(fanOuts, root.getFanOuts());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a uniform tree can be modified with different fan outs at each
|
||||
* level. This is not a persistent operation, just in-memory.
|
||||
*/
|
||||
public void testModifyTree() {
|
||||
int[] fanOuts = {1,2,2,4};
|
||||
int[] newFanOuts = {1,3,1,2};
|
||||
TreeNode root = new TreeNode();
|
||||
root.createTree(fanOuts);
|
||||
assertArrayEquals(fanOuts, root.getFanOuts());
|
||||
|
||||
root.modify(newFanOuts);
|
||||
assertArrayEquals(newFanOuts, root.getFanOuts());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a uniform tree is persisted and later fetched back with same
|
||||
* number of children at every level.
|
||||
*/
|
||||
public void testPersistTree() {
|
||||
int[] fanOuts = {2,3,4};
|
||||
verify(create(fanOuts), fanOuts);
|
||||
}
|
||||
|
||||
public void testAddNodeAtLeaf() {
|
||||
int[] original = {1,2,3};
|
||||
int[] modifier = {1,2,4}; // add new child at Level 2
|
||||
createModifyAndMerge(original, modifier);
|
||||
}
|
||||
|
||||
public void testAddNewLevel() {
|
||||
int[] original = {1,2,3};
|
||||
int[] modifier = {1,2,3,2}; // add 2 new children at new Level
|
||||
createModifyAndMerge(original, modifier);
|
||||
}
|
||||
|
||||
public void testAddAndRemove() {
|
||||
int[] original = {2,3,4};
|
||||
int[] modifier = {4,3,2}; // add 1 at Level 1 + remove 1 at Level 3
|
||||
createModifyAndMerge(original, modifier);
|
||||
}
|
||||
|
||||
public void testAddAtAllLevel() {
|
||||
int[] original = {2,3,4};
|
||||
int[] modifier = {3,4,5}; // add 1 at each Level
|
||||
createModifyAndMerge(original, modifier);
|
||||
}
|
||||
|
||||
public void testRemoveAtAllLevel() {
|
||||
int[] original = {2,3,4};
|
||||
int[] modifier = {1,2,3}; // remove 1 from each Level
|
||||
createModifyAndMerge(original, modifier);
|
||||
}
|
||||
/**
|
||||
* Create a uniform tree with original fanout.
|
||||
* Persist.
|
||||
* Verify in a separate persistence context that the tree is stored.
|
||||
* Modify the tree by adding or deleting nodes according to the given
|
||||
* modified fanouts outside a transaction.
|
||||
* Merge the changes.
|
||||
* Verify that the changes are merged by fetching the modified version.
|
||||
*
|
||||
* @param original
|
||||
* @param modified
|
||||
*/
|
||||
void createModifyAndMerge(int[] original, int[] modifier) {
|
||||
TreeNode root = create(original);
|
||||
|
||||
EntityManager em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
TreeNode modified = em.find(TreeNode.class, root.getId());
|
||||
modified.modify(modifier);
|
||||
em.merge(modified);
|
||||
em.getTransaction().commit();
|
||||
em.clear();
|
||||
|
||||
assertProxyCollection(root.getNodes(), false);
|
||||
|
||||
verify(root, modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a uniform tree with given fan out.
|
||||
* Persist.
|
||||
* Verify that the tree is stored by fetching it in a separate persistence
|
||||
* context.
|
||||
*/
|
||||
TreeNode create(int[] original) {
|
||||
TreeNode root = new TreeNode();
|
||||
root.createTree(original);
|
||||
|
||||
EntityManager em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
em.persist(root);
|
||||
em.getTransaction().commit();
|
||||
em.clear();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void verify(TreeNode node, int[] fanOuts) {
|
||||
EntityManager em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
TreeNode test = em.find(TreeNode.class, node.getId());
|
||||
assertNotNull(test);
|
||||
assertArrayEquals(fanOuts, test.getFanOuts());
|
||||
}
|
||||
|
||||
/** Asserts the given arrays have exactly same elements at the same index.
|
||||
*/
|
||||
void assertArrayEquals(int[] a, int[] b) {
|
||||
assertEquals(a.length, b.length);
|
||||
for (int i = 0; i<a.length; i++)
|
||||
assertEquals(a[i], b[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given object is a proxy collection and whether it is
|
||||
* tracking changes.
|
||||
*/
|
||||
void assertProxyCollection(Object o, boolean tracking) {
|
||||
assertTrue(o instanceof ProxyCollection);
|
||||
ChangeTracker tracker = ((ProxyCollection)o).getChangeTracker();
|
||||
if (tracking) {
|
||||
assertNotNull(tracker);
|
||||
assertTrue(tracker.isTracking());
|
||||
} else {
|
||||
assertFalse(tracker.isTracking());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package org.apache.openjpa.persistence.proxy;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Version;
|
||||
|
||||
import org.apache.openjpa.persistence.DetachedState;
|
||||
import org.apache.openjpa.persistence.ElementDependent;
|
||||
import org.apache.openjpa.persistence.jdbc.ElementJoinColumn;
|
||||
import org.apache.openjpa.persistence.jdbc.OrderColumn;
|
||||
|
||||
/**
|
||||
* Persistent entity for testing adding/removing elements of collection valued
|
||||
* field while in detached state.
|
||||
*
|
||||
* Node refers to a list of Nodes as children.
|
||||
*
|
||||
* Contains recursive methods to create or modify uniform subtree. Uniform
|
||||
* subtree implies that each child at a level L has equal number of
|
||||
* grand children at level L+1.
|
||||
*
|
||||
* @author Pinaki Poddar
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@DetachedState
|
||||
public class TreeNode implements Serializable {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
@ElementJoinColumn(name = "ParentID")
|
||||
@OrderColumn(name = "Sequence")
|
||||
@ElementDependent
|
||||
private List<TreeNode> childern = new ArrayList<TreeNode>();
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a child node at the end of the current list of children.
|
||||
*/
|
||||
public void addNode(TreeNode node) {
|
||||
addNode(node, childern.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a child node at the specified position in the list of children.
|
||||
*/
|
||||
public void addNode(TreeNode node, int position) {
|
||||
checkSequenceRange(position);
|
||||
childern.add(position, node);
|
||||
}
|
||||
|
||||
public boolean removeNode(TreeNode node) {
|
||||
return childern.remove(node);
|
||||
}
|
||||
|
||||
public TreeNode removeNode(int sequence) {
|
||||
checkSequenceRange(sequence);
|
||||
return childern.remove(sequence);
|
||||
}
|
||||
|
||||
public TreeNode getNode(int sequence) {
|
||||
checkSequenceRange(sequence);
|
||||
return childern.get(sequence);
|
||||
}
|
||||
|
||||
public List<TreeNode> getNodes() {
|
||||
return childern;
|
||||
}
|
||||
|
||||
public void clearNodes() {
|
||||
childern.clear();
|
||||
}
|
||||
|
||||
public boolean isLeaf() {
|
||||
return childern.isEmpty();
|
||||
}
|
||||
|
||||
protected void checkSequenceRange(int sequence)
|
||||
throws IllegalArgumentException {
|
||||
int size = childern.size();
|
||||
if (sequence < 0 || sequence > size)
|
||||
throw new IllegalArgumentException("Sequence number is beyond "
|
||||
+ "range of 0 to " + size + ".");
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a uniform subtree below the receiver. Uniform subtree implies that
|
||||
* each child at a level L has equal number of grand children at level L+1.
|
||||
*
|
||||
* @param fanOuts
|
||||
* array of fan outs for children at every level.
|
||||
*/
|
||||
public void createTree(int[] fanOuts) {
|
||||
if (fanOuts.length == 0)
|
||||
return;
|
||||
int[] nextFanOuts = new int[fanOuts.length];
|
||||
System.arraycopy(fanOuts, 1, nextFanOuts, 0, fanOuts.length - 1);
|
||||
for (int j = 0; j < fanOuts[0]; j++) {
|
||||
TreeNode child = new TreeNode();
|
||||
child.setName(getName() + "." + j);
|
||||
addNode(child);
|
||||
child.createTree(nextFanOuts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or remove subtree of the receiver to match the given fanOut.
|
||||
*/
|
||||
public void modify(int[] fanOuts) {
|
||||
if (fanOuts == null || fanOuts.length == 0)
|
||||
return;
|
||||
int n = fanOuts[0];
|
||||
int[] nextFanOuts = new int[fanOuts.length];
|
||||
System.arraycopy(fanOuts, 1, nextFanOuts, 0, fanOuts.length - 1);
|
||||
List<TreeNode> children = getNodes();
|
||||
int diff = children.size() - n;
|
||||
if (diff < 0) {
|
||||
for (int i = 0; i < -diff; i++) {
|
||||
TreeNode newChild = new TreeNode();
|
||||
int position = getNodes().size();
|
||||
newChild.setName(getName() + "." + position);
|
||||
addNode(newChild);
|
||||
}
|
||||
} else if (diff > 0) {
|
||||
for (int i = 0; i < diff; i++) {
|
||||
int position = getNodes().size() - 1;
|
||||
removeNode(position);
|
||||
}
|
||||
}
|
||||
children = getNodes();
|
||||
for (TreeNode child : children) {
|
||||
child.modify(nextFanOuts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fan outs of the given receiver. Assumes that the subtree is
|
||||
* uniform. Otherwise throws exception.
|
||||
*/
|
||||
public int[] getFanOuts() {
|
||||
return getFanOuts(new int[] {});
|
||||
}
|
||||
|
||||
private int[] getFanOuts(int[] list) {
|
||||
List<TreeNode> children = getNodes();
|
||||
if (children.isEmpty())
|
||||
return list;
|
||||
int[] fanOuts = new int[children.size()];
|
||||
int i = 0;
|
||||
for (TreeNode child : children) {
|
||||
fanOuts[i++] = child.getNodes().size();
|
||||
}
|
||||
for (int j = 0; j < fanOuts.length - 1; j++)
|
||||
if (fanOuts[j] != fanOuts[j + 1])
|
||||
throw new RuntimeException("non-uniform fanouts for children "
|
||||
+ " of " + getName());
|
||||
|
||||
int[] newList = new int[list.length + 1];
|
||||
System.arraycopy(list, 0, newList, 0, list.length);
|
||||
newList[list.length] = children.size();
|
||||
return children.get(0).getFanOuts(newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints this receiver and its subtree.
|
||||
*/
|
||||
public void print(PrintStream out) {
|
||||
print(2, out);
|
||||
}
|
||||
|
||||
private void print(int tab, PrintStream out) {
|
||||
for (int i = 0; i < tab; i++)
|
||||
out.print(" ");
|
||||
out.println(getName());
|
||||
for (TreeNode child : getNodes()) {
|
||||
child.print(tab + 2, out);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue