From ac9eb5c27df4e3db17d34365aaa4fcd67e32a984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=9D=B0?= <420482596@qq.com> Date: Wed, 7 Sep 2016 14:02:56 +0800 Subject: [PATCH 1/4] id int to long --- .gitignore | 5 ++++- .../org/code_factory/jpa/nestedset/JpaNode.java | 2 +- .../java/org/code_factory/jpa/nestedset/Key.java | 8 ++++---- .../org/code_factory/jpa/nestedset/NodeInfo.java | 2 +- .../code_factory/jpa/nestedset/model/Category.java | 13 +++++++------ 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 496ee2c..1e2e0f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.DS_Store \ No newline at end of file +.DS_Store +/.idea/ +.idea/ +JpaNestedSet.iml diff --git a/src/main/java/org/code_factory/jpa/nestedset/JpaNode.java b/src/main/java/org/code_factory/jpa/nestedset/JpaNode.java index 345b1f1..9efb6ea 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/JpaNode.java +++ b/src/main/java/org/code_factory/jpa/nestedset/JpaNode.java @@ -60,7 +60,7 @@ public JpaNode(T node, JpaNestedSetManager nsm) { this.type = (Class) node.getClass(); } - @Override public int getId() { + @Override public Long getId() { return this.node.getId(); } diff --git a/src/main/java/org/code_factory/jpa/nestedset/Key.java b/src/main/java/org/code_factory/jpa/nestedset/Key.java index c32ceed..2089010 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/Key.java +++ b/src/main/java/org/code_factory/jpa/nestedset/Key.java @@ -17,9 +17,9 @@ @Immutable class Key { private final Class clazz; - private final int id; + private final Long id; - public Key(Class clazz, int id) { + public Key(Class clazz, Long id) { this.clazz = clazz; this.id = id; } @@ -27,7 +27,7 @@ public Key(Class clazz, int id) { @Override public int hashCode() { int hash = 7; hash = 23 * hash + (this.clazz != null ? this.clazz.hashCode() : 0); - hash = 23 * hash + this.id; + hash = 23 * hash + this.id.intValue(); return hash; } @@ -42,7 +42,7 @@ public Key(Class clazz, int id) { Key otherKey = (Key) other; return this.clazz.equals(otherKey.clazz) - && this.id == otherKey.id; + && this.id.equals(otherKey.id); } @Override public String toString() { diff --git a/src/main/java/org/code_factory/jpa/nestedset/NodeInfo.java b/src/main/java/org/code_factory/jpa/nestedset/NodeInfo.java index 664b326..1ce7a4a 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/NodeInfo.java +++ b/src/main/java/org/code_factory/jpa/nestedset/NodeInfo.java @@ -16,7 +16,7 @@ * @author Roman Borschel */ public interface NodeInfo { - int getId(); + Long getId(); int getLeftValue(); int getRightValue(); int getLevel(); diff --git a/src/test/java/org/code_factory/jpa/nestedset/model/Category.java b/src/test/java/org/code_factory/jpa/nestedset/model/Category.java index f4df51f..b4176b9 100644 --- a/src/test/java/org/code_factory/jpa/nestedset/model/Category.java +++ b/src/test/java/org/code_factory/jpa/nestedset/model/Category.java @@ -5,23 +5,24 @@ package org.code_factory.jpa.nestedset.model; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; import org.code_factory.jpa.nestedset.NodeInfo; import org.code_factory.jpa.nestedset.annotations.LeftColumn; import org.code_factory.jpa.nestedset.annotations.LevelColumn; import org.code_factory.jpa.nestedset.annotations.RightColumn; import org.code_factory.jpa.nestedset.annotations.RootColumn; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + /** * @author robo */ @Entity public class Category implements NodeInfo { @Id @GeneratedValue - private int id; + private Long id; private String name; @Column(updatable=false) @@ -36,7 +37,7 @@ public class Category implements NodeInfo { @RootColumn private int rootId; - @Override public int getId() { + @Override public Long getId() { return this.id; } From 6fc7795f5aee89b685520b6fa1c5e836424d143e Mon Sep 17 00:00:00 2001 From: osoons Date: Wed, 7 Sep 2016 15:18:47 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 37 --- .../code_factory/jpa/nestedset/BasicTest.java | 283 ------------------ .../nestedset/FunctionalNestedSetTest.java | 77 ----- .../jpa/nestedset/MultiRootNodeTest.java | 89 ------ .../jpa/nestedset/model/Category.java | 95 ------ 5 files changed, 581 deletions(-) delete mode 100644 src/test/java/org/code_factory/jpa/nestedset/BasicTest.java delete mode 100644 src/test/java/org/code_factory/jpa/nestedset/FunctionalNestedSetTest.java delete mode 100644 src/test/java/org/code_factory/jpa/nestedset/MultiRootNodeTest.java delete mode 100644 src/test/java/org/code_factory/jpa/nestedset/model/Category.java diff --git a/pom.xml b/pom.xml index 5537e96..643ca5c 100644 --- a/pom.xml +++ b/pom.xml @@ -50,43 +50,12 @@ hibernate-jpa-2.0-api 1.0.1.Final - - org.eclipse.persistence - eclipselink - 2.0.0 - test - - - hsqldb - hsqldb - 1.8.0.7 - test - - - mysql - mysql-connector-java - 5.1.18 - test - - - org.testng - testng - 5.10 - test - jdk15 - javax.inject javax.inject 1 provided - - org.eclipse.persistence - javax.persistence - 2.0.0 - test - net.jcip jcip-annotations @@ -97,12 +66,6 @@ slf4j-api 1.5.10 - - ch.qos.logback - logback-classic - 0.9.18 - test - diff --git a/src/test/java/org/code_factory/jpa/nestedset/BasicTest.java b/src/test/java/org/code_factory/jpa/nestedset/BasicTest.java deleted file mode 100644 index 6f89613..0000000 --- a/src/test/java/org/code_factory/jpa/nestedset/BasicTest.java +++ /dev/null @@ -1,283 +0,0 @@ -/** - * LICENSE - * - * This source file is subject to the MIT license that is bundled - * with this package in the file MIT.txt. - * It is also available through the world-wide-web at this URL: - * http://www.opensource.org/licenses/mit-license.html - */ - -package org.code_factory.jpa.nestedset; - -import java.util.Iterator; -import java.util.List; -import org.code_factory.jpa.nestedset.model.Category; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; -import static org.testng.Assert.*; - -/** - * @author Roman Borschel - */ -public class BasicTest extends FunctionalNestedSetTest { - private Category progCat; - private Category javaCat; - private Category netCat; - - @AfterMethod - @Override protected void closeEntityManager() { - super.closeEntityManager(); - this.progCat = null; - this.javaCat = null; - this.netCat = null; - } - - /** - * Helper method that creates a basic tree that looks as follows: - * - * Programming - * / \ - * Java .NET - * - */ - private void createBasicTree() { - this.progCat = new Category(); - this.progCat.setName("Programming"); - - this.javaCat = new Category(); - this.javaCat.setName("Java"); - - this.netCat = new Category(); - this.netCat.setName(".NET"); - - em.getTransaction().begin(); - Node rootNode = nsm.createRoot(this.progCat); - rootNode.addChild(this.javaCat); - rootNode.addChild(this.netCat); - em.getTransaction().commit(); - em.clear(); - nsm.clear(); - } - - @Test public void testCreateRoot() { - Category cat = new Category(); - cat.setName("Java"); - - em.getTransaction().begin(); - nsm.createRoot(cat); - em.getTransaction().commit(); - em.clear(); - - Category cat2 = em.find(Category.class, cat.getId()); - assert 1 == cat2.getLeftValue(); - assert 2 == cat2.getRightValue(); - assert 0 == cat2.getLevel(); - assert cat != cat2; - assert true == nsm.getNode(cat2).isRoot(); - } - - @Test public void testFetchTree() { - this.createBasicTree(); - - List> tree = nsm.fetchTreeAsList(Category.class); - assert tree.size() == 3; - Iterator> iter = tree.iterator(); - for (int i = 0; i < 3; i++) { - Node node = iter.next(); - if (i == 0) { - assert 1 == node.getLeftValue(); - assert 6 == node.getRightValue(); - assert 0 == node.getLevel(); - } else if (i == 1) { - assert 2 == node.getLeftValue(); - assert 3 == node.getRightValue(); - assert 1 == node.getLevel(); - } else { - assert 4 == node.getLeftValue(); - assert 5 == node.getRightValue(); - assert 1 == node.getLevel(); - } - } - } - - @Test public void testBasicTreeNavigation() { - this.createBasicTree(); - - Category progCat2 = em.find(Category.class, this.progCat.getId()); - - Node progCatNode = nsm.getNode(progCat2); - - assert 1 == progCatNode.getLeftValue(); - assert 6 == progCatNode.getRightValue(); - assert 0 == progCatNode.getLevel(); - assert null == progCatNode.getParent(); - - List> children = progCatNode.getChildren(); - assert 2 == children.size(); - Iterator> childIter = children.iterator(); - Node child1 = childIter.next(); - Node child2 = childIter.next(); - assert 2 == child1.getLeftValue(); - assert 3 == child1.getRightValue(); - assert false == child1.hasChildren(); - assert false == child2.hasChildren(); - assert 0 == child1.getChildren().size(); - assert 0 == child2.getChildren().size(); - - assert progCat2 == child1.getParent().unwrap(); - assert progCat2 == child2.getParent().unwrap(); - - } - - @Test public void testAddingNodesToTree() { - this.createBasicTree(); - - assert 0 == this.nsm.getNodes().size(); - Node root = this.nsm.getNode(em.find(Category.class, this.progCat.getId())); - - // Assert basic tree state, a Programming category with 2 child categories. - assert 1 == root.getLeftValue(); - assert 6 == root.getRightValue(); - assert 2 == root.getChildren().size(); - assert 2 == root.getChildren().size(); - - assert 3 == this.nsm.getNodes().size(); - - // Add PHP category under Programming - em.getTransaction().begin(); - Category phpCat = new Category(); - phpCat.setName("PHP"); - root.addChild(phpCat); - em.getTransaction().commit(); - - assert 6 == phpCat.getLeftValue(); - assert 7 == phpCat.getRightValue(); - assert 8 == root.getRightValue(); - assert 3 == root.getChildren().size(); - - // Add Java EE category under Java - em.getTransaction().begin(); - Category jeeCat = new Category(); - jeeCat.setName("Java EE"); - Node javaNode = this.nsm.getNode(em.find(Category.class, this.javaCat.getId())); - javaNode.addChild(jeeCat); - em.getTransaction().commit(); - - assert 3 == root.getChildren().size(); - assert 3 == jeeCat.getLeftValue(); - assert 4 == jeeCat.getRightValue(); - assert 2 == jeeCat.getLevel(); - assert 2 == javaNode.getLeftValue(); - assert 5 == javaNode.getRightValue(); - assert 10 == root.getRightValue(); - - assert 5 == this.nsm.getNodes().size(); - } - - /** - * Tests creating new nodes and moving them around in a tree. - */ - @Test public void testMovingNodes() { - this.createBasicTree(); - - em.getTransaction().begin(); - - Node progNode = this.nsm.getNode(em.find(Category.class, this.progCat.getId())); - - // Create a new WPF category, placing it under "Programming" first. - /* - Programming - / | \ - Java .NET WPF - */ - Category wpfCat = new Category(); - wpfCat.setName("WPF"); - Node wpfNode = progNode.addChild(wpfCat); - assert 6 == wpfNode.getLeftValue(); - assert 7 == wpfNode.getRightValue(); - assert 1 == wpfNode.getLevel(); - assert 8 == progNode.getRightValue(); - - // Now move it under the .NET category where it really belongs - /* - Programming - / \ - Java .NET - | - WPF - */ - Node netNode = this.nsm.getNode(em.find(Category.class, this.netCat.getId())); - wpfNode.moveAsLastChildOf(netNode); - assert 4 == netNode.getLeftValue(); - assert 7 == netNode.getRightValue(); - assert 5 == wpfNode.getLeftValue(); - assert 6 == wpfNode.getRightValue(); - assert 2 == wpfNode.getLevel(); - assert 8 == progNode.getRightValue(); - - // Create another category "EJB" under "Programming" that doesnt really belong there - /* - Programming - / | \ - Java .NET EJB - | - WPF - */ - Category ejbCat = new Category(); - ejbCat.setName("EJB"); - Node ejbNode = progNode.addChild(ejbCat); - assert 8 == ejbNode.getLeftValue(); - assert 9 == ejbNode.getRightValue(); - assert 10 == progNode.getRightValue(); - - // Move it under "Java" where it belongs - /* - Programming - / \ - Java .NET - | | - EJB WPF - */ - Node javaNode = this.nsm.getNode(em.find(Category.class, this.javaCat.getId())); - ejbNode.moveAsLastChildOf(javaNode); - assert 3 == ejbNode.getLeftValue(); - assert 4 == ejbNode.getRightValue(); - assert 2 == ejbNode.getLevel(); - assert 5 == javaNode.getRightValue(); - assert 6 == netNode.getLeftValue(); - assert 9 == netNode.getRightValue(); - assert 10 == progNode.getRightValue(); - - em.getTransaction().commit(); - } - - @Test - public void testDeleteNode() { - this.createBasicTree(); - - em.getTransaction().begin(); - // fetch the tree - Node progNode = nsm.fetchTree(Category.class, this.progCat.getRootValue()); - assertEquals(progNode.getChildren().size(), 2); - assert 1 == progNode.getLeftValue(); - assert 6 == progNode.getRightValue(); - - // delete the .NET node - Category netCat2 = em.find(Category.class, this.netCat.getId()); - Node netNode = nsm.getNode(netCat2); - netNode.delete(); - - // check in-memory state of tree - assert 1 == progNode.getLeftValue(); - assert 4 == progNode.getRightValue(); - assertFalse(em.contains(netCat2)); - assertTrue(em.contains(progNode.unwrap())); - assertEquals(progNode.getChildren().size(), 1); - try { - nsm.getNode(netCat2); - fail("Retrieving node for deleted category should fail."); - } catch (IllegalArgumentException expected) {} - - em.getTransaction().commit(); - } -} diff --git a/src/test/java/org/code_factory/jpa/nestedset/FunctionalNestedSetTest.java b/src/test/java/org/code_factory/jpa/nestedset/FunctionalNestedSetTest.java deleted file mode 100644 index 33493f9..0000000 --- a/src/test/java/org/code_factory/jpa/nestedset/FunctionalNestedSetTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * LICENSE - * - * This source file is subject to the MIT license that is bundled - * with this package in the file MIT.txt. - * It is also available through the world-wide-web at this URL: - * http://www.opensource.org/licenses/mit-license.html - */ - -package org.code_factory.jpa.nestedset; - -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; -import javax.persistence.Persistence; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; - -/** - * - * @author robo - */ -public class FunctionalNestedSetTest { - protected EntityManagerFactory emFactory; - protected EntityManager em; - protected NestedSetManager nsm; - - @BeforeClass(alwaysRun=true) - protected void createEntityManagerFactory() { - try { - emFactory = Persistence.createEntityManagerFactory("TestPU"); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - @BeforeMethod(alwaysRun=true) - protected void createEntityManager() { - em = emFactory.createEntityManager(); - this.nsm = new JpaNestedSetManager(this.em); - } - - @AfterMethod - protected void closeEntityManager() { - if (em != null) { - em.getTransaction().begin(); - em.createQuery("delete from Category").executeUpdate(); - em.getTransaction().commit(); - em.close(); - } - this.nsm = null; - } - - @AfterClass - protected void closeEntityManagerFactory() { - if (emFactory != null) { - emFactory.close(); - } - } - - protected void printTree(Node node) { - printNode(node); - if (node.hasChildren()) { - for (Node child : node.getChildren()) { - printTree(child); - } - } - } - - protected void printNode(Node node) { - for (int i=0; i - */ -public class MultiRootNodeTest extends FunctionalNestedSetTest { - - @Test - public void testCreateTrees() { - Category javaCat = new Category(); - javaCat.setName("Java"); - javaCat.setRootValue(1); - - Category netCat = new Category(); - netCat.setName(".NET"); - netCat.setRootValue(2); - - Category phpCat = new Category(); - phpCat.setName("PHP"); - phpCat.setRootValue(3); - - em.getTransaction().begin(); - nsm.createRoot(javaCat); - nsm.createRoot(netCat); - nsm.createRoot(phpCat); - em.getTransaction().commit(); - - assert 1 == javaCat.getLeftValue(); - assert 2 == javaCat.getRightValue(); - assert 1 == netCat.getLeftValue(); - assert 2 == netCat.getRightValue(); - assert 1 == phpCat.getLeftValue(); - assert 2 == phpCat.getRightValue(); - - em.getTransaction().begin(); - Node javaNode = nsm.getNode(javaCat); - Category ejbCat = new Category(); - ejbCat.setName("EJB"); - Node ejbNode = javaNode.addChild(ejbCat); - em.getTransaction().commit(); - - assert 1 == javaCat.getLeftValue(); - assert 2 == ejbCat.getLeftValue(); - assert 3 == ejbCat.getRightValue(); - assert 1 == ejbCat.getLevel(); - assert 1 == ejbCat.getRootValue(); - assert 4 == javaCat.getRightValue(); - assert 1 == netCat.getLeftValue(); - assert 2 == netCat.getRightValue(); - assert 1 == phpCat.getLeftValue(); - assert 2 == phpCat.getRightValue(); - - // move between trees - - em.getTransaction().begin(); - Node netNode = nsm.getNode(netCat); - ejbNode.moveAsLastChildOf(netNode); - // Refresh to make sure that we check the database state, not just the in-memory state. - em.refresh(javaCat); - em.refresh(netCat); - em.refresh(phpCat); - em.getTransaction().commit(); - - assert 1 == javaCat.getLeftValue(); - assert 2 == javaCat.getRightValue(); - assert 1 == netNode.getLeftValue(); - assert 2 == ejbNode.getLeftValue(); - assert 3 == ejbNode.getRightValue(); - assert 2 == ejbNode.getRootValue(); - assert 4 == netNode.getRightValue(); - assert 1 == phpCat.getLeftValue(); - assert 2 == phpCat.getRightValue(); - - } - - -} diff --git a/src/test/java/org/code_factory/jpa/nestedset/model/Category.java b/src/test/java/org/code_factory/jpa/nestedset/model/Category.java deleted file mode 100644 index b4176b9..0000000 --- a/src/test/java/org/code_factory/jpa/nestedset/model/Category.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ - -package org.code_factory.jpa.nestedset.model; - -import org.code_factory.jpa.nestedset.NodeInfo; -import org.code_factory.jpa.nestedset.annotations.LeftColumn; -import org.code_factory.jpa.nestedset.annotations.LevelColumn; -import org.code_factory.jpa.nestedset.annotations.RightColumn; -import org.code_factory.jpa.nestedset.annotations.RootColumn; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; - -/** - * @author robo - */ -@Entity -public class Category implements NodeInfo { - @Id @GeneratedValue - private Long id; - private String name; - - @Column(updatable=false) - @LeftColumn - private int lft; - @RightColumn - @Column(updatable=false) - private int rgt; - @LevelColumn - @Column(updatable=false) - private int level; - @RootColumn - private int rootId; - - @Override public Long getId() { - return this.id; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public int getLeftValue() { - return this.lft; - } - - @Override - public int getRightValue() { - return this.rgt; - } - - @Override - public int getLevel() { - return this.level; - } - - @Override - public void setLeftValue(int value) { - this.lft = value; - } - - @Override - public void setRightValue(int value) { - this.rgt = value; - } - - @Override - public void setLevel(int level) { - this.level = level; - } - - @Override - public int getRootValue() { - return this.rootId; - } - - @Override - public void setRootValue(int value) { - this.rootId = value; - } - - @Override public String toString() { - return "[Category: id=" + this.id + ", name=" + this.name + "-" + super.toString() + "]"; - } -} From 0090f34ac25a054145bd43e003632ee826e717b5 Mon Sep 17 00:00:00 2001 From: osoons Date: Wed, 7 Sep 2016 15:28:12 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9hibernate-jpa=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 643ca5c..b9aacf0 100644 --- a/pom.xml +++ b/pom.xml @@ -47,8 +47,8 @@ org.hibernate.javax.persistence - hibernate-jpa-2.0-api - 1.0.1.Final + hibernate-jpa-2.1-api + 1.0.0.Final javax.inject From 65365a96f3a3fda708c732ca80b65f29e1db4c69 Mon Sep 17 00:00:00 2001 From: osoons Date: Fri, 7 Oct 2016 11:19:45 +0800 Subject: [PATCH 4/4] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E6=94=B9NodeInfo?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=B1=9E=E6=80=A7=E7=9A=84=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?(int=20->=20Integer,=20root=20->=20Long)=EF=BC=9B=202=E3=80=81?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=BF=94=E5=9B=9EAdiacency=20List(=E4=B8=B4?= =?UTF-8?q?=E6=8E=A5=E8=A1=A8)=E6=8E=A5=E5=8F=A3=EF=BC=8C=E8=BF=94?= =?UTF-8?q?=E5=9B=9Eid=20parent=5Fid=E7=BB=93=E6=9E=84;=203=E3=80=81?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B5=8B=E8=AF=95=E7=B1=BB;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 51 ++- .../jpa/nestedset/JpaNestedSetManager.java | 105 ++++-- .../code_factory/jpa/nestedset/JpaNode.java | 90 +++--- .../org/code_factory/jpa/nestedset/Key.java | 4 +- .../jpa/nestedset/NestedSetManager.java | 16 +- .../org/code_factory/jpa/nestedset/Node.java | 1 - .../code_factory/jpa/nestedset/NodeInfo.java | 16 +- .../code_factory/jpa/nestedset/BasicTest.java | 298 ++++++++++++++++++ .../nestedset/FunctionalNestedSetTest.java | 78 +++++ .../jpa/nestedset/MultiRootNodeTest.java | 89 ++++++ .../jpa/nestedset/model/Category.java | 125 ++++++++ src/test/resources/META-INF/persistence.xml | 30 +- 12 files changed, 797 insertions(+), 106 deletions(-) create mode 100755 src/test/java/org/code_factory/jpa/nestedset/BasicTest.java create mode 100755 src/test/java/org/code_factory/jpa/nestedset/FunctionalNestedSetTest.java create mode 100755 src/test/java/org/code_factory/jpa/nestedset/MultiRootNodeTest.java create mode 100755 src/test/java/org/code_factory/jpa/nestedset/model/Category.java diff --git a/pom.xml b/pom.xml index b9aacf0..dc60dd6 100644 --- a/pom.xml +++ b/pom.xml @@ -14,8 +14,8 @@ maven-compiler-plugin 2.1 - 1.6 - 1.6 + 1.7 + 1.7 true true true @@ -64,8 +64,53 @@ org.slf4j slf4j-api - 1.5.10 + 1.7.21 + + org.hibernate + hibernate-core + 4.3.11.Final + provided + + + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + + + + + org.hibernate + hibernate-entitymanager + 4.3.11.Final + provided + + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + + + + + junit + junit + 4.12 + test + + + org.hsqldb + hsqldb + 2.2.9 + test + + diff --git a/src/main/java/org/code_factory/jpa/nestedset/JpaNestedSetManager.java b/src/main/java/org/code_factory/jpa/nestedset/JpaNestedSetManager.java index 42ac618..d32e9e5 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/JpaNestedSetManager.java +++ b/src/main/java/org/code_factory/jpa/nestedset/JpaNestedSetManager.java @@ -1,6 +1,6 @@ /** * LICENSE - * + *

* This source file is subject to the MIT license that is bundled * with this package in the file MIT.txt. * It is also available through the world-wide-web at this URL: @@ -22,15 +22,18 @@ import javax.inject.Inject; import javax.persistence.Entity; import javax.persistence.EntityManager; +import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; + import net.jcip.annotations.NotThreadSafe; import org.code_factory.jpa.nestedset.annotations.LeftColumn; import org.code_factory.jpa.nestedset.annotations.LevelColumn; import org.code_factory.jpa.nestedset.annotations.RightColumn; import org.code_factory.jpa.nestedset.annotations.RootColumn; +import org.hibernate.transform.AliasToEntityMapResultTransformer; /** * @author Roman Borschel @@ -41,6 +44,7 @@ public class JpaNestedSetManager implements NestedSetManager { private final EntityManager em; private final Map> nodes; private final Map, Configuration> configs; + private final Long defaultRootId = 1L; @Inject public JpaNestedSetManager(EntityManager em) { @@ -78,14 +82,14 @@ public Collection> getNodes() { */ @Override public List> fetchTreeAsList(Class clazz) { - return fetchTreeAsList(clazz, 0); + return fetchTreeAsList(clazz, defaultRootId); } /** * {@inheritDoc} */ @Override - public List> fetchTreeAsList(Class clazz, int rootId) { + public List> fetchTreeAsList(Class clazz, Long rootId) { Configuration config = getConfig(clazz); CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(clazz); @@ -104,17 +108,54 @@ public List> fetchTreeAsList(Class clazz, int ro return tree; } + @Override + public List> fetchTreeAsAdjacencyList(Class clazz) { + return fetchTreeAsAdjacencyList(clazz, defaultRootId, 0); + } + + @Override + public List> fetchTreeAsAdjacencyList(Class clazz, Long rootId) { + return fetchTreeAsAdjacencyList(clazz, rootId, 0); + } + + @Override + public List> fetchTreeAsAdjacencyList(Class clazz, Long rootId, int maxLevel) { + Configuration config = getConfig(clazz); + + StringBuilder sb = new StringBuilder(); + sb.append("SELECT node.*,") + .append(" parent.id parent_id ") + .append(" FROM ").append(config.getEntityName()).append(" node ") + .append(" JOIN ").append(config.getEntityName()).append(" parent ") + .append(" ON node.").append(config.getLeftFieldName()) + .append(" BETWEEN parent.").append(config.getLeftFieldName()).append(" AND parent.").append(config.getRightFieldName()) + .append(" WHERE node.").append(config.getLevelFieldName()).append(" = parent.").append(config.getLevelFieldName()).append(" + 1") + .append(" AND node.").append(config.getRootIdFieldName()).append(" = ").append(rootId); + + if (maxLevel > 0) { + sb.append(" AND node.").append(config.getLevelFieldName()).append(" < ").append(maxLevel); + } + + Query query = em.createNativeQuery(sb.toString()); + + org.hibernate.Query hibernateQuery = ((org.hibernate.jpa.HibernateQuery) query) + .getHibernateQuery(); + hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE); + + return hibernateQuery.list(); + } + /** * {@inheritDoc} */ @Override - public Node fetchTree(Class clazz, int rootId) { + public Node fetchTree(Class clazz, Long rootId) { return fetchTreeAsList(clazz, rootId).get(0); } @Override public Node fetchTree(Class clazz) { - return fetchTree(clazz, 0); + return fetchTree(clazz, defaultRootId); } /** @@ -178,10 +219,13 @@ public Node createRoot(T root) { root.setLeftValue(maximumRight + 1); root.setRightValue(maximumRight + 2); root.setLevel(0); + if (root.getRootValue() == null) { + root.setRootValue(defaultRootId); + } em.persist(root); return getNode(root); } - + /** * {@inheritDoc} */ @@ -213,22 +257,19 @@ public Node getNode(T nodeInfo) { Configuration getConfig(Class clazz) { if (!this.configs.containsKey(clazz)) { Configuration config = new Configuration(); - + Entity entity = clazz.getAnnotation(Entity.class); - String name = entity.name(); - config.setEntityName( (name != null && name.length()>0) ? name : clazz.getSimpleName()); + String name = entity.name(); + config.setEntityName((name != null && name.length() > 0) ? name : clazz.getSimpleName()); for (Field field : clazz.getDeclaredFields()) { if (field.getAnnotation(LeftColumn.class) != null) { config.setLeftFieldName(field.getName()); - } - else if (field.getAnnotation(RightColumn.class) != null) { + } else if (field.getAnnotation(RightColumn.class) != null) { config.setRightFieldName(field.getName()); - } - else if (field.getAnnotation(LevelColumn.class) != null) { + } else if (field.getAnnotation(LevelColumn.class) != null) { config.setLevelFieldName(field.getName()); - } - else if (field.getAnnotation(RootColumn.class) != null) { + } else if (field.getAnnotation(RootColumn.class) != null) { config.setRootIdFieldName(field.getName()); } } @@ -238,18 +279,18 @@ else if (field.getAnnotation(RootColumn.class) != null) { return this.configs.get(clazz); } - + int getMaximumRight(Class clazz) { - Configuration config = getConfig(clazz); - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(clazz); + Configuration config = getConfig(clazz); + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(clazz); Root queryRoot = cq.from(clazz); cq.orderBy(cb.desc(queryRoot.get(config.getRightFieldName()))); - ListhighestRows = em.createQuery(cq).setMaxResults(1).getResultList(); + List highestRows = em.createQuery(cq).setMaxResults(1).getResultList(); if (highestRows.isEmpty()) { - return 0; + return 0; } else { - return highestRows.get(0).getRightValue(); + return highestRows.get(0).getRightValue(); } } @@ -260,9 +301,9 @@ int getMaximumRight(Class clazz) { * @param cq * @param rootId */ - void applyRootId(Class clazz, CriteriaQuery cq, int rootId) { + void applyRootId(Class clazz, CriteriaQuery cq, Long rootId) { Configuration config = getConfig(clazz); - if (config.getRootIdFieldName() != null) { + if (config.getRootIdFieldName() != null && rootId != null) { Root root = cq.getRoots().iterator().next(); CriteriaBuilder cb = em.getCriteriaBuilder(); Predicate p = cq.getRestriction(); @@ -276,9 +317,9 @@ void applyRootId(Class clazz, CriteriaQuery cq, int rootId) { * * @param minLeft The lower bound (inclusive) of the left values to update. * @param maxLeft The upper bound (inclusive) of the left values to update. - * @param delta The delta to apply on the left values within the range. + * @param delta The delta to apply on the left values within the range. */ - void updateLeftValues(int minLeft, int maxLeft, int delta, int rootId) { + void updateLeftValues(int minLeft, int maxLeft, int delta, Long rootId) { for (Node node : this.nodes.values()) { if (node.getRootValue() == rootId) { if (node.getLeftValue() >= minLeft && (maxLeft == 0 || node.getLeftValue() <= maxLeft)) { @@ -295,9 +336,9 @@ void updateLeftValues(int minLeft, int maxLeft, int delta, int rootId) { * * @param minRight The lower bound (inclusive) of the right values to update. * @param maxRight The upper bound (inclusive) of the right values to update. - * @param delta The delta to apply on the right values within the range. + * @param delta The delta to apply on the right values within the range. */ - void updateRightValues(int minRight, int maxRight, int delta, int rootId) { + void updateRightValues(int minRight, int maxRight, int delta, Long rootId) { for (Node node : this.nodes.values()) { if (node.getRootValue() == rootId) { if (node.getRightValue() >= minRight && (maxRight == 0 || node.getRightValue() <= maxRight)) { @@ -312,11 +353,11 @@ void updateRightValues(int minRight, int maxRight, int delta, int rootId) { * INTERNAL: * Updates the level values of all nodes currently known to the manager. * - * @param left The lower bound left value. + * @param left The lower bound left value. * @param right The upper bound right value. * @param delta The delta to apply on the level values of the nodes within the range. */ - void updateLevels(int left, int right, int delta, int rootId) { + void updateLevels(int left, int right, int delta, Long rootId) { for (Node node : this.nodes.values()) { if (node.getRootValue() == rootId) { if (node.getLeftValue() > left && node.getRightValue() < right) { @@ -327,7 +368,7 @@ void updateLevels(int left, int right, int delta, int rootId) { } } - void removeNodes(int left, int right, int rootId) { + void removeNodes(int left, int right, Long rootId) { Set removed = new HashSet(); for (Node node : this.nodes.values()) { if (node.getRootValue() == rootId) { @@ -341,7 +382,7 @@ void removeNodes(int left, int right, int rootId) { n.setLeftValue(0); n.setRightValue(0); n.setLevel(0); - n.setRootValue(0); + n.setRootValue(null); this.em.detach(n.unwrap()); } } diff --git a/src/main/java/org/code_factory/jpa/nestedset/JpaNode.java b/src/main/java/org/code_factory/jpa/nestedset/JpaNode.java index 9efb6ea..1a3345c 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/JpaNode.java +++ b/src/main/java/org/code_factory/jpa/nestedset/JpaNode.java @@ -64,35 +64,35 @@ public JpaNode(T node, JpaNestedSetManager nsm) { return this.node.getId(); } - @Override public int getLeftValue() { + @Override public Integer getLeftValue() { return this.node.getLeftValue(); } - @Override public int getRightValue() { + @Override public Integer getRightValue() { return this.node.getRightValue(); } - @Override public int getLevel() { + @Override public Integer getLevel() { return this.node.getLevel(); } - @Override public int getRootValue() { + @Override public Long getRootValue() { return this.node.getRootValue(); } - @Override public void setRootValue(int value) { + @Override public void setRootValue(Long value) { this.node.setRootValue(value); } - @Override public void setLeftValue(int value) { + @Override public void setLeftValue(Integer value) { this.node.setLeftValue(value); } - @Override public void setRightValue(int value) { + @Override public void setRightValue(Integer value) { this.node.setRightValue(value); } - @Override public void setLevel(int level) { + @Override public void setLevel(Integer level) { this.node.setLevel(level); } @@ -294,9 +294,9 @@ public Node getParent() { throw new IllegalArgumentException("Cannot add node as child of itself."); } - int newLeft = getRightValue(); - int newRight = getRightValue() + 1; - int newRoot = getRootValue(); + Integer newLeft = getRightValue(); + Integer newRight = getRightValue() + 1; + Long newRoot = getRootValue(); shiftRLValues(newLeft, 0, 2, newRoot); child.setLevel(getLevel() + 1); @@ -318,9 +318,9 @@ private void insertAsPrevSiblingOf(Node dest) { throw new IllegalArgumentException("Cannot add node as child of itself."); } - int newLeft = dest.getLeftValue(); - int newRight = dest.getLeftValue() + 1; - int newRoot = dest.getRootValue(); + Integer newLeft = dest.getLeftValue(); + Integer newRight = dest.getLeftValue() + 1; + Long newRoot = dest.getRootValue(); shiftRLValues(newLeft, 0, 2, newRoot); setLevel(dest.getLevel()); @@ -340,9 +340,9 @@ private void insertAsNextSiblingOf(Node dest) { throw new IllegalArgumentException("Cannot add node as child of itself."); } - int newLeft = dest.getRightValue() + 1; - int newRight = dest.getRightValue() + 2; - int newRoot = dest.getRootValue(); + Integer newLeft = dest.getRightValue() + 1; + Integer newRight = dest.getRightValue() + 2; + Long newRoot = dest.getRootValue(); shiftRLValues(newLeft, 0, 2, newRoot); setLevel(dest.getLevel()); @@ -362,9 +362,9 @@ private void insertAsLastChildOf(Node dest) { throw new IllegalArgumentException("Cannot add node as child of itself."); } - int newLeft = dest.getRightValue(); - int newRight = dest.getRightValue() + 1; - int newRoot = dest.getRootValue(); + Integer newLeft = dest.getRightValue(); + Integer newRight = dest.getRightValue() + 1; + Long newRoot = dest.getRootValue(); shiftRLValues(newLeft, 0, 2, newRoot); setLevel(dest.getLevel() + 1); @@ -384,9 +384,9 @@ private void insertAsFirstChildOf(Node dest) { throw new IllegalArgumentException("Cannot add node as child of itself."); } - int newLeft = dest.getLeftValue() + 1; - int newRight = dest.getLeftValue() + 2; - int newRoot = dest.getRootValue(); + Integer newLeft = dest.getLeftValue() + 1; + Integer newRight = dest.getLeftValue() + 2; + Long newRoot = dest.getRootValue(); shiftRLValues(newLeft, 0, 2, newRoot); setLevel(dest.getLevel()); @@ -402,7 +402,7 @@ private void insertAsFirstChildOf(Node dest) { @Override public void delete() { //TODO: Remove deleted nodes that are in-memory from JpaNestedSetManager. - int oldRoot = getRootValue(); + Long oldRoot = getRootValue(); Configuration cfg = nsm.getConfig(this.type); String rootIdFieldName = cfg.getRootIdFieldName(); String leftFieldName = cfg.getLeftFieldName(); @@ -445,7 +445,7 @@ public void delete() { * @param delta The offset by which to shift the left/right values (can be negative). * @param rootId The root/tree ID of the nodes to shift. */ - private void shiftRLValues(int first, int last, int delta, int rootId) { + private void shiftRLValues(int first, int last, int delta, Long rootId) { Configuration cfg = nsm.getConfig(this.type); String rootIdFieldName = cfg.getRootIdFieldName(); String leftFieldName = cfg.getLeftFieldName(); @@ -462,7 +462,7 @@ private void shiftRLValues(int first, int last, int delta, int rootId) { sbLeft.append(" and n.").append(leftFieldName).append(" <= ?3"); } - if (rootIdFieldName != null) { + if (rootIdFieldName != null && rootId != null) { sbLeft.append(" and n.").append(rootIdFieldName).append(" = ?4"); } @@ -472,7 +472,7 @@ private void shiftRLValues(int first, int last, int delta, int rootId) { if (last > 0) { qLeft.setParameter(3, last); } - if (rootIdFieldName != null) { + if (rootIdFieldName != null && rootId != null) { qLeft.setParameter(4, rootId); } qLeft.executeUpdate(); @@ -488,7 +488,7 @@ private void shiftRLValues(int first, int last, int delta, int rootId) { sbRight.append(" and n.").append(rightFieldName).append(" <= ?3"); } - if (rootIdFieldName != null) { + if (rootIdFieldName != null && rootId != null) { sbRight.append(" and n.").append(rootIdFieldName).append(" = ?4"); } @@ -498,7 +498,7 @@ private void shiftRLValues(int first, int last, int delta, int rootId) { if (last > 0) { qRight.setParameter(3, last); } - if (rootIdFieldName != null) { + if (rootIdFieldName != null && rootId != null) { qRight.setParameter(4, rootId); } qRight.executeUpdate(); @@ -631,13 +631,13 @@ public void moveAsPrevSiblingOf(Node dest) { /** * move node's and its children to location 'destLeft' and update rest of tree. * - * @param int destLeft destination left value + * @param destLeft destLeft destination left value * @param levelDiff */ private void updateNode(int destLeft, int levelDiff) { - int left = getLeftValue(); - int right = getRightValue(); - int rootId = getRootValue(); + Integer left = getLeftValue(); + Integer right = getRightValue(); + Long rootId = getRootValue(); int treeSize = right - left + 1; // Make room in the new branch @@ -756,11 +756,11 @@ private void moveBetweenTrees(Node dest, int newLeftValue, int moveType) { String entityName = cfg.getEntityName(); // Move between trees: Detach from old tree & insert into new tree - int newRoot = dest.getRootValue(); - int oldRoot = getRootValue(); - int oldLft = getLeftValue(); - int oldRgt = getRightValue(); - int oldLevel = getLevel(); + Long newRoot = dest.getRootValue(); + Long oldRoot = getRootValue(); + Integer oldLft = getLeftValue(); + Integer oldRgt = getRightValue(); + Integer oldLevel = getLevel(); // Prepare target tree for insertion, make room shiftRLValues(newLeftValue, 0, oldRgt - oldLft - 1, newRoot); @@ -831,7 +831,7 @@ private void moveBetweenTrees(Node dest, int newLeftValue, int moveType) { * * @param newRootId */ - public void makeRoot(int newRootId) { + public void makeRoot(Long newRootId) { if (isRoot()) { return; } @@ -842,15 +842,15 @@ public void makeRoot(int newRootId) { String levelFieldName = cfg.getLevelFieldName(); String rootIdFieldName = cfg.getRootIdFieldName(); String entityName = cfg.getEntityName(); - - int oldRgt = getRightValue(); - int oldLft = getLeftValue(); - int oldRoot = getRootValue(); - int oldLevel = getLevel(); + + Integer oldRgt = getRightValue(); + Integer oldLft = getLeftValue(); + Long oldRoot = getRootValue(); + Integer oldLevel = getLevel(); // Update descendants lft/rgt/root/level values int diff = 1 - oldLft; - int newRoot = newRootId; + Long newRoot = newRootId; StringBuilder updateQuery = new StringBuilder(); updateQuery.append("update ").append(entityName).append(" n") diff --git a/src/main/java/org/code_factory/jpa/nestedset/Key.java b/src/main/java/org/code_factory/jpa/nestedset/Key.java index 2089010..4f5cae3 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/Key.java +++ b/src/main/java/org/code_factory/jpa/nestedset/Key.java @@ -11,6 +11,8 @@ import net.jcip.annotations.Immutable; +import java.util.Objects; + /** * @author Roman Borschel */ @@ -27,7 +29,7 @@ public Key(Class clazz, Long id) { @Override public int hashCode() { int hash = 7; hash = 23 * hash + (this.clazz != null ? this.clazz.hashCode() : 0); - hash = 23 * hash + this.id.intValue(); + hash = 23 * hash + this.id.hashCode(); return hash; } diff --git a/src/main/java/org/code_factory/jpa/nestedset/NestedSetManager.java b/src/main/java/org/code_factory/jpa/nestedset/NestedSetManager.java index 5689d76..fd534d9 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/NestedSetManager.java +++ b/src/main/java/org/code_factory/jpa/nestedset/NestedSetManager.java @@ -11,6 +11,7 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import javax.persistence.EntityManager; /** @@ -46,14 +47,13 @@ public interface NestedSetManager { * @param rootId * @return The root node of the tree. */ - Node fetchTree(Class clazz, int rootId); + Node fetchTree(Class clazz, Long rootId); /** * Fetches the complete tree, returning the root node of the tree. * * @param * @param clazz - * @param rootId * @return The root node of the tree. */ Node fetchTree(Class clazz); @@ -75,7 +75,17 @@ public interface NestedSetManager { * @param rootId * @return The tree in form of a list, starting with the root node. */ - List> fetchTreeAsList(Class clazz, int rootId); + List> fetchTreeAsList(Class clazz, Long rootId); + + /** + * + * @param clazz + * @param + * @return + */ + List> fetchTreeAsAdjacencyList(Class clazz); + List> fetchTreeAsAdjacencyList(Class clazz, Long rootId); + List> fetchTreeAsAdjacencyList(Class clazz, Long rootId, int maxLevel); /** * Gets the EntityManager used by this NestedSetManager. diff --git a/src/main/java/org/code_factory/jpa/nestedset/Node.java b/src/main/java/org/code_factory/jpa/nestedset/Node.java index 6f61c0e..cfc305f 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/Node.java +++ b/src/main/java/org/code_factory/jpa/nestedset/Node.java @@ -67,7 +67,6 @@ public interface Node extends NodeInfo { /** * Gets all ancestors of this node. * - * @param int depth The depth "upstairs". * @return The ancestors of the node. */ List> getAncestors(); diff --git a/src/main/java/org/code_factory/jpa/nestedset/NodeInfo.java b/src/main/java/org/code_factory/jpa/nestedset/NodeInfo.java index 1ce7a4a..bd28572 100644 --- a/src/main/java/org/code_factory/jpa/nestedset/NodeInfo.java +++ b/src/main/java/org/code_factory/jpa/nestedset/NodeInfo.java @@ -17,12 +17,12 @@ */ public interface NodeInfo { Long getId(); - int getLeftValue(); - int getRightValue(); - int getLevel(); - int getRootValue(); - void setLeftValue(int value); - void setRightValue(int value); - void setLevel(int level); - void setRootValue(int value); + Integer getLeftValue(); + Integer getRightValue(); + Integer getLevel(); + Long getRootValue(); + void setLeftValue(Integer value); + void setRightValue(Integer value); + void setLevel(Integer level); + void setRootValue(Long value); } diff --git a/src/test/java/org/code_factory/jpa/nestedset/BasicTest.java b/src/test/java/org/code_factory/jpa/nestedset/BasicTest.java new file mode 100755 index 0000000..559b78e --- /dev/null +++ b/src/test/java/org/code_factory/jpa/nestedset/BasicTest.java @@ -0,0 +1,298 @@ +/** + * LICENSE + *

+ * This source file is subject to the MIT license that is bundled + * with this package in the file MIT.txt. + * It is also available through the world-wide-web at this URL: + * http://www.opensource.org/licenses/mit-license.html + */ + +package org.code_factory.jpa.nestedset; + +import org.code_factory.jpa.nestedset.model.Category; +import org.junit.After; +import org.junit.Test; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * @author Roman Borschel + */ +public class BasicTest extends FunctionalNestedSetTest { + private Category progCat; + private Category javaCat; + private Category netCat; + + @After + @Override + public void closeEntityManager() { + super.closeEntityManager(); + this.progCat = null; + this.javaCat = null; + this.netCat = null; + } + + /** + * Helper method that creates a basic tree that looks as follows: + * + * Programming + * / \ + * Java .NET + * + */ + private void createBasicTree() { + this.progCat = new Category(); + this.progCat.setName("Programming"); + + this.javaCat = new Category(); + this.javaCat.setName("Java"); + + this.netCat = new Category(); + this.netCat.setName(".NET"); + + em.getTransaction().begin(); + Node rootNode = nsm.createRoot(this.progCat); + rootNode.addChild(this.javaCat); + rootNode.addChild(this.netCat); + em.getTransaction().commit(); + em.clear(); + nsm.clear(); + } + + @Test + public void testCreateRoot() { + Category cat = new Category(); + cat.setName("Java"); + //cat.setId(1L); + + em.getTransaction().begin(); + nsm.createRoot(cat); + em.getTransaction().commit(); + em.clear(); + System.out.println(cat); + Category cat2 = em.find(Category.class, cat.getId()); + assert 1 == cat2.getLeftValue(); + assert 2 == cat2.getRightValue(); + assert 0 == cat2.getLevel(); + assert cat != cat2; + assert true == nsm.getNode(cat2).isRoot(); + } + + @Test + public void testFetchTree() { + this.createBasicTree(); + + List> tree = nsm.fetchTreeAsList(Category.class); + assert tree.size() == 3; + Iterator> iter = tree.iterator(); + for (int i = 0; i < 3; i++) { + Node node = iter.next(); + if (i == 0) { + assert 1 == node.getLeftValue(); + assert 6 == node.getRightValue(); + assert 0 == node.getLevel(); + } else if (i == 1) { + assert 2 == node.getLeftValue(); + assert 3 == node.getRightValue(); + assert 1 == node.getLevel(); + } else { + assert 4 == node.getLeftValue(); + assert 5 == node.getRightValue(); + assert 1 == node.getLevel(); + } + System.out.println(node); + } + + List> list = nsm.fetchTreeAsAdjacencyList(Category.class); + System.out.println(list.toString()); + } + + @Test + public void testBasicTreeNavigation() { + this.createBasicTree(); + + Category progCat2 = em.find(Category.class, this.progCat.getId()); + + Node progCatNode = nsm.getNode(progCat2); + + assert 1 == progCatNode.getLeftValue(); + assert 6 == progCatNode.getRightValue(); + assert 0 == progCatNode.getLevel(); + assert null == progCatNode.getParent(); + + List> children = progCatNode.getChildren(); + assert 2 == children.size(); + Iterator> childIter = children.iterator(); + Node child1 = childIter.next(); + Node child2 = childIter.next(); + assert 2 == child1.getLeftValue(); + assert 3 == child1.getRightValue(); + assert false == child1.hasChildren(); + assert false == child2.hasChildren(); + assert 0 == child1.getChildren().size(); + assert 0 == child2.getChildren().size(); + + assert progCat2 == child1.getParent().unwrap(); + assert progCat2 == child2.getParent().unwrap(); + + } + + @Test + public void testAddingNodesToTree() { + this.createBasicTree(); + + assert 0 == this.nsm.getNodes().size(); + Node root = this.nsm.getNode(em.find(Category.class, this.progCat.getId())); + + // Assert basic tree state, a Programming category with 2 child categories. + assert 1 == root.getLeftValue(); + assert 6 == root.getRightValue(); + assert 2 == root.getChildren().size(); + assert 2 == root.getChildren().size(); + + assert 3 == this.nsm.getNodes().size(); + + // Add PHP category under Programming + em.getTransaction().begin(); + Category phpCat = new Category(); + phpCat.setName("PHP"); + root.addChild(phpCat); + em.getTransaction().commit(); + + assert 6 == phpCat.getLeftValue(); + assert 7 == phpCat.getRightValue(); + assert 8 == root.getRightValue(); + assert 3 == root.getChildren().size(); + + // Add Java EE category under Java + em.getTransaction().begin(); + Category jeeCat = new Category(); + jeeCat.setName("Java EE"); + Node javaNode = this.nsm.getNode(em.find(Category.class, this.javaCat.getId())); + javaNode.addChild(jeeCat); + em.getTransaction().commit(); + + assert 3 == root.getChildren().size(); + assert 3 == jeeCat.getLeftValue(); + assert 4 == jeeCat.getRightValue(); + assert 2 == jeeCat.getLevel(); + assert 2 == javaNode.getLeftValue(); + assert 5 == javaNode.getRightValue(); + assert 10 == root.getRightValue(); + + assert 5 == this.nsm.getNodes().size(); + } + + /** + * Tests creating new nodes and moving them around in a tree. + */ + @Test + public void testMovingNodes() { + this.createBasicTree(); + + em.getTransaction().begin(); + + Node progNode = this.nsm.getNode(em.find(Category.class, this.progCat.getId())); + + // Create a new WPF category, placing it under "Programming" first. + /* + Programming + / | \ + Java .NET WPF + */ + Category wpfCat = new Category(); + wpfCat.setName("WPF"); + Node wpfNode = progNode.addChild(wpfCat); + assert 6 == wpfNode.getLeftValue(); + assert 7 == wpfNode.getRightValue(); + assert 1 == wpfNode.getLevel(); + assert 8 == progNode.getRightValue(); + + // Now move it under the .NET category where it really belongs + /* + Programming + / \ + Java .NET + | + WPF + */ + Node netNode = this.nsm.getNode(em.find(Category.class, this.netCat.getId())); + wpfNode.moveAsLastChildOf(netNode); + assert 4 == netNode.getLeftValue(); + assert 7 == netNode.getRightValue(); + assert 5 == wpfNode.getLeftValue(); + assert 6 == wpfNode.getRightValue(); + assert 2 == wpfNode.getLevel(); + assert 8 == progNode.getRightValue(); + + // Create another category "EJB" under "Programming" that doesnt really belong there + /* + Programming + / | \ + Java .NET EJB + | + WPF + */ + Category ejbCat = new Category(); + ejbCat.setName("EJB"); + Node ejbNode = progNode.addChild(ejbCat); + assert 8 == ejbNode.getLeftValue(); + assert 9 == ejbNode.getRightValue(); + assert 10 == progNode.getRightValue(); + + // Move it under "Java" where it belongs + /* + Programming + / \ + Java .NET + | | + EJB WPF + */ + Node javaNode = this.nsm.getNode(em.find(Category.class, this.javaCat.getId())); + ejbNode.moveAsLastChildOf(javaNode); + assert 3 == ejbNode.getLeftValue(); + assert 4 == ejbNode.getRightValue(); + assert 2 == ejbNode.getLevel(); + assert 5 == javaNode.getRightValue(); + assert 6 == netNode.getLeftValue(); + assert 9 == netNode.getRightValue(); + assert 10 == progNode.getRightValue(); + + em.getTransaction().commit(); + } + + @Test + public void testDeleteNode() { + this.createBasicTree(); + + em.getTransaction().begin(); + // fetch the tree + Node progNode = nsm.fetchTree(Category.class, this.progCat.getRootValue()); + assertEquals(progNode.getChildren().size(), 2); + assert 1 == progNode.getLeftValue(); + assert 6 == progNode.getRightValue(); + + // delete the .NET node + Category netCat2 = em.find(Category.class, this.netCat.getId()); + Node netNode = nsm.getNode(netCat2); + netNode.delete(); + + // check in-memory state of tree + assert 1 == progNode.getLeftValue(); + assert 4 == progNode.getRightValue(); + assertFalse(em.contains(netCat2)); + assertTrue(em.contains(progNode.unwrap())); + assertEquals(progNode.getChildren().size(), 1); + try { + nsm.getNode(netCat2); + fail("Retrieving node for deleted category should fail."); + } catch (IllegalArgumentException expected) { + } + + em.getTransaction().commit(); + } +} diff --git a/src/test/java/org/code_factory/jpa/nestedset/FunctionalNestedSetTest.java b/src/test/java/org/code_factory/jpa/nestedset/FunctionalNestedSetTest.java new file mode 100755 index 0000000..ea9aa12 --- /dev/null +++ b/src/test/java/org/code_factory/jpa/nestedset/FunctionalNestedSetTest.java @@ -0,0 +1,78 @@ +/** + * LICENSE + * + * This source file is subject to the MIT license that is bundled + * with this package in the file MIT.txt. + * It is also available through the world-wide-web at this URL: + * http://www.opensource.org/licenses/mit-license.html + */ + +package org.code_factory.jpa.nestedset; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +/** + * + * @author robo + */ +public class FunctionalNestedSetTest { + private static EntityManagerFactory emFactory; + protected EntityManager em; + protected NestedSetManager nsm; + + @BeforeClass + public static void createEntityManagerFactory() { + try { + emFactory = Persistence.createEntityManagerFactory("TestPU"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + @Before + public void createEntityManager() { + em = emFactory.createEntityManager(); + this.nsm = new JpaNestedSetManager(this.em); + } + + @After + public void closeEntityManager() { + if (em != null) { + em.getTransaction().begin(); + em.createQuery("delete from Category").executeUpdate(); + em.getTransaction().commit(); + em.close(); + } + this.nsm = null; + } + + @AfterClass + public static void closeEntityManagerFactory() { + if (emFactory != null) { + emFactory.close(); + } + } + + protected void printTree(Node node) { + printNode(node); + if (node.hasChildren()) { + for (Node child : node.getChildren()) { + printTree(child); + } + } + } + + protected void printNode(Node node) { + for (int i=0; i + */ +public class MultiRootNodeTest extends FunctionalNestedSetTest { + + @Test + public void testCreateTrees() { + Category javaCat = new Category(); + javaCat.setName("Java"); + javaCat.setRootValue(1L); + + Category netCat = new Category(); + netCat.setName(".NET"); + netCat.setRootValue(2L); + + Category phpCat = new Category(); + phpCat.setName("PHP"); + phpCat.setRootValue(3L); + + em.getTransaction().begin(); + nsm.createRoot(javaCat); + nsm.createRoot(netCat); + nsm.createRoot(phpCat); + em.getTransaction().commit(); + + assert 1 == javaCat.getLeftValue(); + assert 2 == javaCat.getRightValue(); + assert 1 == netCat.getLeftValue(); + assert 2 == netCat.getRightValue(); + assert 1 == phpCat.getLeftValue(); + assert 2 == phpCat.getRightValue(); + + em.getTransaction().begin(); + Node javaNode = nsm.getNode(javaCat); + Category ejbCat = new Category(); + ejbCat.setName("EJB"); + Node ejbNode = javaNode.addChild(ejbCat); + em.getTransaction().commit(); + + assert 1 == javaCat.getLeftValue(); + assert 2 == ejbCat.getLeftValue(); + assert 3 == ejbCat.getRightValue(); + assert 1 == ejbCat.getLevel(); + assert 1 == ejbCat.getRootValue(); + assert 4 == javaCat.getRightValue(); + assert 1 == netCat.getLeftValue(); + assert 2 == netCat.getRightValue(); + assert 1 == phpCat.getLeftValue(); + assert 2 == phpCat.getRightValue(); + + // move between trees + + em.getTransaction().begin(); + Node netNode = nsm.getNode(netCat); + ejbNode.moveAsLastChildOf(netNode); + // Refresh to make sure that we check the database state, not just the in-memory state. + em.refresh(javaCat); + em.refresh(netCat); + em.refresh(phpCat); + em.getTransaction().commit(); + + assert 1 == javaCat.getLeftValue(); + assert 2 == javaCat.getRightValue(); + assert 1 == netNode.getLeftValue(); + assert 2 == ejbNode.getLeftValue(); + assert 3 == ejbNode.getRightValue(); + assert 2 == ejbNode.getRootValue(); + assert 4 == netNode.getRightValue(); + assert 1 == phpCat.getLeftValue(); + assert 2 == phpCat.getRightValue(); + + } + + +} diff --git a/src/test/java/org/code_factory/jpa/nestedset/model/Category.java b/src/test/java/org/code_factory/jpa/nestedset/model/Category.java new file mode 100755 index 0000000..8e738fc --- /dev/null +++ b/src/test/java/org/code_factory/jpa/nestedset/model/Category.java @@ -0,0 +1,125 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.code_factory.jpa.nestedset.model; + +import javax.persistence.*; + +import org.code_factory.jpa.nestedset.NodeInfo; +import org.code_factory.jpa.nestedset.annotations.LeftColumn; +import org.code_factory.jpa.nestedset.annotations.LevelColumn; +import org.code_factory.jpa.nestedset.annotations.RightColumn; +import org.code_factory.jpa.nestedset.annotations.RootColumn; + +/** + * @author robo + */ +@Entity +public class Category implements NodeInfo { + @Id + @GeneratedValue + private Long id; + + private String name; + + @Column(updatable=false) + @LeftColumn + private Integer lft; + @RightColumn + @Column(updatable=false) + private Integer rgt; + @LevelColumn + @Column(updatable=false) + private Integer level; + @RootColumn + private Long rootId; + + @Override public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public Integer getLeftValue() { + return this.lft; + } + + @Override + public Integer getRightValue() { + return this.rgt; + } + + @Override + public Integer getLevel() { + return this.level; + } + + @Override + public void setLeftValue(Integer value) { + this.lft = value; + } + + @Override + public void setRightValue(Integer value) { + this.rgt = value; + } + + @Override + public void setLevel(Integer level) { + this.level = level; + } + + @Override + public Long getRootValue() { + return this.rootId; + } + + @Override + public void setRootValue(Long value) { + this.rootId = value; + } + + @Override public String toString() { + return "[Category: id=" + this.id + ", name=" + this.name + "-" + super.toString() + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Category category = (Category) o; + + if (!id.equals(category.id)) return false; + if (!name.equals(category.name)) return false; + if (!lft.equals(category.lft)) return false; + if (!rgt.equals(category.rgt)) return false; + if (!level.equals(category.level)) return false; + return rootId != null ? rootId.equals(category.rootId) : category.rootId == null; + + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + name.hashCode(); + result = 31 * result + lft.hashCode(); + result = 31 * result + rgt.hashCode(); + result = 31 * result + level.hashCode(); + result = 31 * result + (rootId != null ? rootId.hashCode() : 0); + return result; + } +} diff --git a/src/test/resources/META-INF/persistence.xml b/src/test/resources/META-INF/persistence.xml index 9a7b92b..6270489 100644 --- a/src/test/resources/META-INF/persistence.xml +++ b/src/test/resources/META-INF/persistence.xml @@ -1,25 +1,29 @@ - org.eclipse.persistence.jpa.PersistenceProvider + org.hibernate.jpa.HibernatePersistenceProvider org.code_factory.jpa.nestedset.model.Category - - - - - - - + + + + + + + + + + + +