From a7464c7e0a4162d4a574d3d31190b3ac11828eb2 Mon Sep 17 00:00:00 2001 From: Pinaki Poddar Date: Tue, 5 Aug 2008 19:07:34 +0000 Subject: [PATCH] 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 --- .../proxy/TestProxyCollection.java | 165 ++++++++++++++ .../openjpa/persistence/proxy/TreeNode.java | 213 ++++++++++++++++++ 2 files changed, 378 insertions(+) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TreeNode.java diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java new file mode 100644 index 000000000..1e7c8c098 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java @@ -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 + * OPENJPA-628 + * + * @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 childern = new ArrayList(); + + @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 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 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 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); + } + } + +}