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:
Pinaki Poddar 2008-08-05 19:07:34 +00:00
parent e41e1dd976
commit a7464c7e0a
2 changed files with 378 additions and 0 deletions

View File

@ -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());
}
}
}

View File

@ -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);
}
}
}