/* * TestFetchPlan.java * * Created on October 16, 2006, 3:02 PM * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.openjpa.persistence.kernel; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.openjpa.persistence.kernel.common.apps.PCAddress; import org.apache.openjpa.persistence.kernel.common.apps.PCCompany; import org.apache.openjpa.persistence.kernel.common.apps.PCCountry; import org.apache.openjpa.persistence.kernel.common.apps.PCDepartment; import org.apache.openjpa.persistence.kernel.common.apps.PCDirectory; import org.apache.openjpa.persistence.kernel.common.apps.PCEmployee; import org.apache.openjpa.persistence.kernel.common.apps.PCFile; import org.apache.openjpa.persistence.kernel.common.apps.PCPerson; import org.apache.openjpa.persistence.FetchPlan; import org.apache.openjpa.persistence.OpenJPAEntityManager; import org.apache.openjpa.persistence.OpenJPAQuery; public class TestFetchPlan extends BaseKernelTest { static Object _rootDirId; static Object _rootCompanyId; static final int MAX_DEPTH = 5; // Maximum depth of the directories static final int MAX_CHILD = 3; // Maximum number of files/directory static final String quote = "\""; private static boolean firstTime = true; /** * Creates a new instance of TestFetchPlan */ public TestFetchPlan() { } public TestFetchPlan(String name) { super(name); } /** * Clears past data and creates new data for test. * Clear test data before and not <em>after</em> such that one can analyze * the database for test failures. */ public void setUp() throws Exception { if (firstTime) { firstTime = false; clearTestData(); createTestData(); } } /** * Create a directory tree of MAX_DEPTH with each directory having a single * directory and MAX_CHILD files. * Creates typical Employee-Department-Company-Address instances. * * @return the persitent identifier of the root directory. */ void createTestData() { // create a tree of directories with files in them PCDirectory rootDir = new PCDirectory(getDirectoryName(0)); PCDirectory parent = rootDir; for (int i = 1; i <= MAX_DEPTH; i++) { PCDirectory dir = new PCDirectory(getDirectoryName(i)); parent.add(dir); for (int j = 0; j < MAX_CHILD; j++) parent.add(getFileName(j)); parent = dir; } // create a graph // | ---address-country // | // company-dept-employee-address-country // PCCountry country1 = new PCCountry("100", "Employee 1 Country"); PCCountry country2 = new PCCountry("200", "Employee 2 Country"); PCCountry ccountry = new PCCountry("300", "Company Country"); PCCompany company = new PCCompany("Company"); PCDepartment dept1 = new PCDepartment("Department1"); PCDepartment dept2 = new PCDepartment("Department2"); PCEmployee emp1 = new PCEmployee("Employee1"); PCEmployee emp2 = new PCEmployee("Employee2"); PCAddress addr1 = new PCAddress("Street1", "city1", country1); PCAddress addr2 = new PCAddress("Street2", "city2", country2); PCAddress caddr = new PCAddress("Street3", "city3", ccountry); dept1.addEmployee(emp1); dept2.addEmployee(emp2); company.addDepartment(dept1); company.addDepartment(dept2); company.setAddress(caddr); emp1.setAddress(addr1); emp2.setAddress(addr2); OpenJPAEntityManager pm = getPM(); startTx(pm); pm.persist(rootDir); pm.persist(company); // _rootDirId = pm.getObjectId(rootDir); _rootDirId = rootDir.getId(); assertNotNull(_rootDirId); // _rootCompanyId = pm.getObjectId(company); _rootCompanyId = company.getId(); assertNotNull(_rootCompanyId); endTx(pm); endEm(pm); } /** * Test that the single valued field (_parent) is not traversed when the * fecth group selects only the _name field. */ public void testZeroRecursionDepthSingleValuedField() { genericTestForSingleValuedRecursiveField("name", 4, 0); } /** * Test that the single valued field (_parent) is traversed once and only * once when the fecth group selects the _parent field with recursion depth * of 1 (default). */ public void testOneRecursionDepthSingleValuedField() { genericTestForSingleValuedRecursiveField("name+parent", 4, 1); } /** * Test that the single valued field (_parent) is traversed twice and only * twice when the fecth group selects the _parent field with recursion depth * of 2. */ public void testTwoRecursionDepthSingleValuedField() { genericTestForSingleValuedRecursiveField("name+parent+grandparent", 4, 2); } public void testThreeRecursionDepthSingleValuedField() { genericTestForSingleValuedRecursiveField ("name+parent+grandparent+greatgrandparent", 4, 3); } public void testInfiniteRecursionDepthSingleValuedField() { genericTestForSingleValuedRecursiveField("allparents", 4, -1); } /** * Generically tests recursive traversal of single-valued parent field. * * @param plan a plan that fetches L parents and no children * @param rd the recursion depth of directory from the root * @param fd the fetch depth = number of parents fetched */ public void genericTestForSingleValuedRecursiveField(String plan, int rd, int fd) { PCDirectory result = queryDirectoryWithPlan(plan, rd, fd); checkParents(result, rd, fd); Object children = PCDirectory.reflect(result, "_children"); assertNull(children); } /** * Query to obtain a single directory at the given depth. * The directory name is constructed by the depth it occurs (d0 for root, * d1 for depth 1 and so on).<BR> * Checks the result for for matching name and size of the result (must * be one). * * @param plan name of a fetch plan * @param depth depth of the directory to be queried * @return the selected directory. */ PCDirectory queryDirectoryWithPlan(String plan, int rd, int fd) { OpenJPAEntityManager pm = getPM(); pm.getFetchPlan().addFetchGroup(plan); if (fd != 0) pm.getFetchPlan().setMaxFetchDepth(fd); // String filter = "_name == " + quoted(getDirectoryName(rd)); // OpenJPAQuery query = pm.createNativeQuery(filter,PCDirectory.class); // List result = (List) query.getResultList(); String query = "SELECT o FROM PCDirectory o WHERE o._name = '" + getDirectoryName(rd) + "'"; List fresult = ((OpenJPAQuery) pm.createQuery(query)).getResultList(); assertEquals(1, fresult.size()); PCDirectory dir = (PCDirectory) fresult.get(0); return dir; } /** * Asserts that * <LI> the given directory name matches the directory name at depth D. * <LI> the parents upto L recursion is not null and beyond is * null. * * @param result a directory to test * @param D depth at which this directory appears * @param L the number of live (fetched) parents. -1 denotes infinite */ void checkParents(PCDirectory result, int D, int L) { assertEquals("ge", getDirectoryName(D), PCDirectory.reflect(result, "_name")); PCDirectory[] parents = getParents(result, D); int N = (L == -1) ? D : L; for (int i = 0; i < N; i++) { assertNotNull(i + "-th parent at depth " + D + " is null", parents[i]); assertEquals(getDirectoryName(D - i - 1), PCDirectory.reflect(parents[i], "_name")); } for (int i = N; i < D; i++) assertNull(i + "-th parent at depth " + D + " is not null " + parents[i], parents[i]); } /** * Gets an array of parents of the given directory. The zeroth element * is the parent of the given directory and (i+1)-th element is the * parent of the i-th element. Uses reflection to ensure that the * side-effect does not cause a database access for the field. * * @param dir a starting directory * @param depth depth to recurse. must be positive. */ PCDirectory[] getParents(PCDirectory dir, int depth) { PCDirectory[] result = new PCDirectory[depth]; PCDirectory current = dir; for (int i = 0; i < depth; i++) { result[i] = (PCDirectory) PCDirectory.reflect(current, "_parent"); current = result[i]; } return result; } /** * Checks that the first L elements of the given array is non-null and * the rest are null. * * @param depth */ void assertNullParent(PCDirectory[] parents, int L) { for (int i = 0; i < L; i++) assertNotNull(parents[i]); for (int i = L; i < parents.length; i++) assertNull(parents[i]); } String getDirectoryName(int depth) { return "d" + depth; } String getFileName(int depth) { return "f" + depth; } String quoted(String s) { return quote + s + quote; } /** * Defines a fetch plan that has several fetch groups to traverse a chain * of relationships. * After getting the root by an extent query, checks (by reflection) that * all the relations in the chain are fetched. * The fetch depth is kept infinite, so what would be fetched is essentially * controlled by the fetch groups. */ public void testRelationTraversal() { OpenJPAEntityManager pm = getPM(); FetchPlan plan = pm.getFetchPlan(); pm.getFetchPlan().setMaxFetchDepth(-1); plan.addFetchGroup("employee.department"); plan.addFetchGroup("department.company"); plan.addFetchGroup("company.address"); plan.addFetchGroup("address.country"); Iterator employees = pm.createExtent(PCEmployee.class, true).iterator(); while (employees.hasNext()) { PCEmployee emp = (PCEmployee) employees.next(); PCDepartment dept = (PCDepartment) PCEmployee.reflect(emp, "department"); assertNotNull(dept); PCCompany company = (PCCompany) PCDepartment.reflect(dept, "company"); assertNotNull(company); PCAddress addr = (PCAddress) PCCompany.reflect(company, "address"); assertNotNull(addr); PCCountry country = (PCCountry) PCAddress.reflect(addr, "country"); assertNotNull(country); } } /** * Defines a fetch plan that has several fetch groups to traverse a chain * of relationships but truncated at the last relation. * After getting the root by an extent query, checks (by reflection) that * all but the last relation in the chain are fetched. * The fetch depth is kept infinite, so what would be fetched is essentially * controlled by the fetch groups. */ public void testRelationTraversalTruncated() { OpenJPAEntityManager pm = getPM(); FetchPlan plan = pm.getFetchPlan(); pm.getFetchPlan().setMaxFetchDepth(-1); plan.addFetchGroup("employee.department"); plan.addFetchGroup("department.company"); plan.addFetchGroup("company.address"); Iterator employees = pm.createExtent(PCEmployee.class, true).iterator(); while (employees.hasNext()) { PCEmployee emp = (PCEmployee) employees.next(); PCDepartment dept = (PCDepartment) PCEmployee.reflect(emp, "department"); assertNotNull(dept); PCCompany company = (PCCompany) PCDepartment.reflect(dept, "company"); assertNotNull(company); PCAddress addr = (PCAddress) PCCompany.reflect(company, "address"); assertNotNull(addr); PCCountry country = (PCCountry) PCAddress.reflect(addr, "country"); assertNull(country); } } /** * Gets a Compnay object by getObjectById() method as opposed to query. * The active fetch groups should bring in the multi-valued relationships. * The address->country relationship can be reached in two alternate * paths -- one as company->address->country and the other is * company->department->employee->address->country. * Though active fetch groups allow both the paths -- the max fetch depth * is set such that the shorter path is taken but not the longer one. * Hence the company's address->country should be loaded but not the * employee's. */ public void testRelationTraversalWithCompanyAsRoot() { OpenJPAEntityManager pm = getPM(); FetchPlan plan = pm.getFetchPlan(); plan.setMaxFetchDepth(2); plan.addFetchGroup("company.departments"); plan.addFetchGroup("company.address"); plan.addFetchGroup("department.employees"); plan.addFetchGroup("person.address"); plan.addFetchGroup("address.country"); PCCompany company = (PCCompany) pm.find(PCCompany.class, _rootCompanyId); Set departments = (Set) PCCompany.reflect(company, "departments"); assertNotNull("department is null", departments); assertEquals("exp. depart size is not 2", 2, departments.size()); PCDepartment dept = (PCDepartment) departments.iterator().next(); assertNotNull("dept is null", dept); Set employees = (Set) PCDepartment.reflect(dept, "employees"); assertNotNull("employees is null", employees); assertEquals(1, employees.size()); PCEmployee emp = (PCEmployee) employees.iterator().next(); assertNotNull("emp is not null", emp); PCAddress eaddr = (PCAddress) PCPerson.reflect(emp, "address"); PCAddress caddr = (PCAddress) PCCompany.reflect(company, "address"); assertNull("eaddr is not null", eaddr); assertNotNull("caddr is null", caddr); PCCountry country = (PCCountry) PCAddress.reflect(caddr, "country"); assertNotNull("country is null", country); } /** * Same as above but the root compnay instance is detached. */ public void testDetachedRelationTraversalWithCompanyAsRoot() { OpenJPAEntityManager pm = getPM(); FetchPlan plan = pm.getFetchPlan(); pm.getFetchPlan().setMaxFetchDepth(2); plan.addFetchGroup("company.departments"); plan.addFetchGroup("company.address"); plan.addFetchGroup("department.employees"); plan.addFetchGroup("person.address"); plan.addFetchGroup("address.country"); PCCompany company1 = (PCCompany) pm.find(PCCompany.class, _rootCompanyId); PCCompany company = (PCCompany) pm.detachCopy(company1); assertTrue("company is equal company1", company != company1); Set departments = (Set) PCCompany.reflect(company, "departments"); assertNotNull("department is null", departments); assertEquals("department size is not 2", 2, departments.size()); PCDepartment dept = (PCDepartment) departments.iterator().next(); assertNotNull("dept is null", dept); Set employees = (Set) PCDepartment.reflect(dept, "employees"); assertNotNull("employee is null", employees); assertEquals("employees size not 1", 1, employees.size()); PCEmployee emp = (PCEmployee) employees.iterator().next(); assertNotNull("emp is null", emp); PCAddress eaddr = (PCAddress) PCPerson.reflect(emp, "address"); PCAddress caddr = (PCAddress) PCCompany.reflect(company, "address"); assertNull("eaddr is not null", eaddr); assertNotNull("caddr is null", caddr); PCCountry country = (PCCountry) PCAddress.reflect(caddr, "country"); assertNotNull("country is null", country); } public void testDefaultFetchGroup() { OpenJPAEntityManager pm = getPM(); String squery = "SELECT DISTINCT o FROM PCEmployee o WHERE o.name = 'Employee1'"; OpenJPAQuery q = pm.createQuery(squery); //FIXME jthomas PCEmployee person = (PCEmployee) q.getSingleResult(); assertEquals("Exp. String is not employee1", "Employee1", PCPerson.reflect(person, "name")); } public void testDefaultFetchGroupExistsByDefault() { OpenJPAEntityManager pm = getPM(); assertTrue("pm does not contain default fetchplan", pm.getFetchPlan().getFetchGroups().contains( FetchPlan.GROUP_DEFAULT)); } public void testDefaultFetchGroupCanBeRemoved() { OpenJPAEntityManager pm = getPM(); assertTrue("does not contain default fetchplan", pm.getFetchPlan().getFetchGroups().contains( FetchPlan.GROUP_DEFAULT)); pm.getFetchPlan().removeFetchGroup(FetchPlan.GROUP_DEFAULT); assertFalse("does contain default fetchplan", pm.getFetchPlan().getFetchGroups().contains( FetchPlan.GROUP_DEFAULT)); OpenJPAEntityManager pm2 = getPM(); assertTrue("pm2 does not contain default fetchplan", pm2.getFetchPlan().getFetchGroups().contains( FetchPlan.GROUP_DEFAULT)); } public void tearDown() throws Exception { super.tearDown(); } private void clearTestData() throws Exception { // OpenJPAEntityManagerFactory pmf = // (OpenJPAEntityManagerFactory) getEmf(); // OpenJPAConfiguration conf=pmf.getConfiguration(); // // Class.forName(pmf.getConfiguration().getConnection2DriverName()); // String url=conf.getConnection2URL(); // String user=conf.getConnection2UserName(); // String pass=conf.getConnection2Password(); // // Connection con = DriverManager.getConnection( // url, // user, // pass); // con.setAutoCommit(true); // con.prepareStatement("DELETE FROM PCDIRECTORY").executeUpdate(); // con.prepareStatement("DELETE FROM PCFILE").executeUpdate(); // con.prepareStatement("DELETE FROM PCPERSON").executeUpdate(); // con.prepareStatement("DELETE FROM PCDEPARTMENT").executeUpdate(); // con.prepareStatement("DELETE FROM PCCOMPANY").executeUpdate(); // con.prepareStatement("DELETE FROM PCADDRESS").executeUpdate(); // con.prepareStatement("DELETE FROM PCCOUNTRY").executeUpdate(); deleteAll(PCDirectory.class); deleteAll(PCFile.class); deleteAll(PCPerson.class); deleteAll(PCDepartment.class); deleteAll(PCCompany.class); deleteAll(PCAddress.class); deleteAll(PCCountry.class); deleteAll(PCEmployee.class); } }