001: /*
002: * TestFetchPlan.java
003: *
004: * Created on October 16, 2006, 3:02 PM
005: *
006: * To change this template, choose Tools | Template Manager
007: * and open the template in the editor.
008: */
009: /*
010: * Licensed to the Apache Software Foundation (ASF) under one
011: * or more contributor license agreements. See the NOTICE file
012: * distributed with this work for additional information
013: * regarding copyright ownership. The ASF licenses this file
014: * to you under the Apache License, Version 2.0 (the
015: * "License"); you may not use this file except in compliance
016: * with the License. You may obtain a copy of the License at
017: *
018: * http://www.apache.org/licenses/LICENSE-2.0
019: *
020: * Unless required by applicable law or agreed to in writing,
021: * software distributed under the License is distributed on an
022: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
023: * KIND, either express or implied. See the License for the
024: * specific language governing permissions and limitations
025: * under the License.
026: */
027: package org.apache.openjpa.persistence.kernel;
028:
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Set;
032:
033: import org.apache.openjpa.persistence.kernel.common.apps.PCAddress;
034: import org.apache.openjpa.persistence.kernel.common.apps.PCCompany;
035: import org.apache.openjpa.persistence.kernel.common.apps.PCCountry;
036: import org.apache.openjpa.persistence.kernel.common.apps.PCDepartment;
037: import org.apache.openjpa.persistence.kernel.common.apps.PCDirectory;
038: import org.apache.openjpa.persistence.kernel.common.apps.PCEmployee;
039: import org.apache.openjpa.persistence.kernel.common.apps.PCFile;
040: import org.apache.openjpa.persistence.kernel.common.apps.PCPerson;
041:
042: import org.apache.openjpa.persistence.FetchPlan;
043: import org.apache.openjpa.persistence.OpenJPAEntityManager;
044: import org.apache.openjpa.persistence.OpenJPAQuery;
045:
046: public class TestFetchPlan extends BaseKernelTest {
047:
048: static Object _rootDirId;
049: static Object _rootCompanyId;
050:
051: static final int MAX_DEPTH = 5; // Maximum depth of the directories
052: static final int MAX_CHILD = 3; // Maximum number of files/directory
053: static final String quote = "\"";
054: private static boolean firstTime = true;
055:
056: /**
057: * Creates a new instance of TestFetchPlan
058: */
059: public TestFetchPlan() {
060: }
061:
062: public TestFetchPlan(String name) {
063: super (name);
064: }
065:
066: /**
067: * Clears past data and creates new data for test.
068: * Clear test data before and not <em>after</em> such that one can analyze
069: * the database for test failures.
070: */
071: public void setUp() throws Exception {
072: if (firstTime) {
073: firstTime = false;
074: clearTestData();
075: createTestData();
076: }
077: }
078:
079: /**
080: * Create a directory tree of MAX_DEPTH with each directory having a single
081: * directory and MAX_CHILD files.
082: * Creates typical Employee-Department-Company-Address instances.
083: *
084: * @return the persitent identifier of the root directory.
085: */
086: void createTestData() {
087: // create a tree of directories with files in them
088: PCDirectory rootDir = new PCDirectory(getDirectoryName(0));
089: PCDirectory parent = rootDir;
090: for (int i = 1; i <= MAX_DEPTH; i++) {
091: PCDirectory dir = new PCDirectory(getDirectoryName(i));
092: parent.add(dir);
093:
094: for (int j = 0; j < MAX_CHILD; j++)
095: parent.add(getFileName(j));
096:
097: parent = dir;
098: }
099:
100: // create a graph
101: // | ---address-country
102: // |
103: // company-dept-employee-address-country
104: //
105: PCCountry country1 = new PCCountry("100", "Employee 1 Country");
106: PCCountry country2 = new PCCountry("200", "Employee 2 Country");
107: PCCountry ccountry = new PCCountry("300", "Company Country");
108:
109: PCCompany company = new PCCompany("Company");
110:
111: PCDepartment dept1 = new PCDepartment("Department1");
112: PCDepartment dept2 = new PCDepartment("Department2");
113:
114: PCEmployee emp1 = new PCEmployee("Employee1");
115: PCEmployee emp2 = new PCEmployee("Employee2");
116:
117: PCAddress addr1 = new PCAddress("Street1", "city1", country1);
118: PCAddress addr2 = new PCAddress("Street2", "city2", country2);
119: PCAddress caddr = new PCAddress("Street3", "city3", ccountry);
120:
121: dept1.addEmployee(emp1);
122: dept2.addEmployee(emp2);
123:
124: company.addDepartment(dept1);
125: company.addDepartment(dept2);
126:
127: company.setAddress(caddr);
128:
129: emp1.setAddress(addr1);
130: emp2.setAddress(addr2);
131:
132: OpenJPAEntityManager pm = getPM();
133: startTx(pm);
134: pm.persist(rootDir);
135: pm.persist(company);
136: // _rootDirId = pm.getObjectId(rootDir);
137: _rootDirId = rootDir.getId();
138: assertNotNull(_rootDirId);
139: // _rootCompanyId = pm.getObjectId(company);
140: _rootCompanyId = company.getId();
141: assertNotNull(_rootCompanyId);
142: endTx(pm);
143: endEm(pm);
144: }
145:
146: /**
147: * Test that the single valued field (_parent) is not traversed when the
148: * fecth group selects only the _name field.
149: */
150: public void testZeroRecursionDepthSingleValuedField() {
151: genericTestForSingleValuedRecursiveField("name", 4, 0);
152: }
153:
154: /**
155: * Test that the single valued field (_parent) is traversed once and only
156: * once when the fecth group selects the _parent field with recursion depth
157: * of 1 (default).
158: */
159: public void testOneRecursionDepthSingleValuedField() {
160: genericTestForSingleValuedRecursiveField("name+parent", 4, 1);
161: }
162:
163: /**
164: * Test that the single valued field (_parent) is traversed twice and only
165: * twice when the fecth group selects the _parent field with recursion depth
166: * of 2.
167: */
168: public void testTwoRecursionDepthSingleValuedField() {
169: genericTestForSingleValuedRecursiveField(
170: "name+parent+grandparent", 4, 2);
171: }
172:
173: public void testThreeRecursionDepthSingleValuedField() {
174: genericTestForSingleValuedRecursiveField(
175: "name+parent+grandparent+greatgrandparent", 4, 3);
176: }
177:
178: public void testInfiniteRecursionDepthSingleValuedField() {
179: genericTestForSingleValuedRecursiveField("allparents", 4, -1);
180: }
181:
182: /**
183: * Generically tests recursive traversal of single-valued parent field.
184: *
185: * @param plan a plan that fetches L parents and no children
186: * @param rd the recursion depth of directory from the root
187: * @param fd the fetch depth = number of parents fetched
188: */
189: public void genericTestForSingleValuedRecursiveField(String plan,
190: int rd, int fd) {
191: PCDirectory result = queryDirectoryWithPlan(plan, rd, fd);
192:
193: checkParents(result, rd, fd);
194:
195: Object children = PCDirectory.reflect(result, "_children");
196: assertNull(children);
197: }
198:
199: /**
200: * Query to obtain a single directory at the given depth.
201: * The directory name is constructed by the depth it occurs (d0 for root,
202: * d1 for depth 1 and so on).<BR>
203: * Checks the result for for matching name and size of the result (must
204: * be one).
205: *
206: * @param plan name of a fetch plan
207: * @param depth depth of the directory to be queried
208: * @return the selected directory.
209: */
210: PCDirectory queryDirectoryWithPlan(String plan, int rd, int fd) {
211: OpenJPAEntityManager pm = getPM();
212: pm.getFetchPlan().addFetchGroup(plan);
213: if (fd != 0)
214: pm.getFetchPlan().setMaxFetchDepth(fd);
215:
216: // String filter = "_name == " + quoted(getDirectoryName(rd));
217: // OpenJPAQuery query = pm.createNativeQuery(filter,PCDirectory.class);
218: // List result = (List) query.getResultList();
219:
220: String query = "SELECT o FROM PCDirectory o WHERE o._name = '"
221: + getDirectoryName(rd) + "'";
222: List fresult = ((OpenJPAQuery) pm.createQuery(query))
223: .getResultList();
224:
225: assertEquals(1, fresult.size());
226: PCDirectory dir = (PCDirectory) fresult.get(0);
227:
228: return dir;
229: }
230:
231: /**
232: * Asserts that
233: * <LI> the given directory name matches the directory name at depth D.
234: * <LI> the parents upto L recursion is not null and beyond is
235: * null.
236: *
237: * @param result a directory to test
238: * @param D depth at which this directory appears
239: * @param L the number of live (fetched) parents. -1 denotes infinite
240: */
241: void checkParents(PCDirectory result, int D, int L) {
242:
243: assertEquals("ge", getDirectoryName(D), PCDirectory.reflect(
244: result, "_name"));
245: PCDirectory[] parents = getParents(result, D);
246: int N = (L == -1) ? D : L;
247: for (int i = 0; i < N; i++) {
248: assertNotNull(i + "-th parent at depth " + D + " is null",
249: parents[i]);
250: assertEquals(getDirectoryName(D - i - 1), PCDirectory
251: .reflect(parents[i], "_name"));
252: }
253: for (int i = N; i < D; i++)
254: assertNull(i + "-th parent at depth " + D + " is not null "
255: + parents[i], parents[i]);
256: }
257:
258: /**
259: * Gets an array of parents of the given directory. The zeroth element
260: * is the parent of the given directory and (i+1)-th element is the
261: * parent of the i-th element. Uses reflection to ensure that the
262: * side-effect does not cause a database access for the field.
263: *
264: * @param dir a starting directory
265: * @param depth depth to recurse. must be positive.
266: * @return
267: */
268: PCDirectory[] getParents(PCDirectory dir, int depth) {
269: PCDirectory[] result = new PCDirectory[depth];
270: PCDirectory current = dir;
271: for (int i = 0; i < depth; i++) {
272: result[i] = (PCDirectory) PCDirectory.reflect(current,
273: "_parent");
274: current = result[i];
275: }
276: return result;
277: }
278:
279: /**
280: * Checks that the first L elements of the given array is non-null and
281: * the rest are null.
282: *
283: * @param depth
284: */
285: void assertNullParent(PCDirectory[] parents, int L) {
286: for (int i = 0; i < L; i++)
287: assertNotNull(parents[i]);
288: for (int i = L; i < parents.length; i++)
289: assertNull(parents[i]);
290: }
291:
292: String getDirectoryName(int depth) {
293: return "d" + depth;
294: }
295:
296: String getFileName(int depth) {
297: return "f" + depth;
298: }
299:
300: String quoted(String s) {
301: return quote + s + quote;
302: }
303:
304: /**
305: * Defines a fetch plan that has several fetch groups to traverse a chain
306: * of relationships.
307: * After getting the root by an extent query, checks (by reflection) that
308: * all the relations in the chain are fetched.
309: * The fetch depth is kept infinite, so what would be fetched is essentially
310: * controlled by the fetch groups.
311: */
312: public void testRelationTraversal() {
313: OpenJPAEntityManager pm = getPM();
314: FetchPlan plan = pm.getFetchPlan();
315: pm.getFetchPlan().setMaxFetchDepth(-1);
316: plan.addFetchGroup("employee.department");
317: plan.addFetchGroup("department.company");
318: plan.addFetchGroup("company.address");
319: plan.addFetchGroup("address.country");
320:
321: Iterator employees = pm.createExtent(PCEmployee.class, true)
322: .iterator();
323: while (employees.hasNext()) {
324: PCEmployee emp = (PCEmployee) employees.next();
325:
326: PCDepartment dept = (PCDepartment) PCEmployee.reflect(emp,
327: "department");
328: assertNotNull(dept);
329:
330: PCCompany company = (PCCompany) PCDepartment.reflect(dept,
331: "company");
332: assertNotNull(company);
333:
334: PCAddress addr = (PCAddress) PCCompany.reflect(company,
335: "address");
336: assertNotNull(addr);
337:
338: PCCountry country = (PCCountry) PCAddress.reflect(addr,
339: "country");
340: assertNotNull(country);
341: }
342: }
343:
344: /**
345: * Defines a fetch plan that has several fetch groups to traverse a chain
346: * of relationships but truncated at the last relation.
347: * After getting the root by an extent query, checks (by reflection) that
348: * all but the last relation in the chain are fetched.
349: * The fetch depth is kept infinite, so what would be fetched is essentially
350: * controlled by the fetch groups.
351: */
352: public void testRelationTraversalTruncated() {
353: OpenJPAEntityManager pm = getPM();
354: FetchPlan plan = pm.getFetchPlan();
355: pm.getFetchPlan().setMaxFetchDepth(-1);
356: plan.addFetchGroup("employee.department");
357: plan.addFetchGroup("department.company");
358: plan.addFetchGroup("company.address");
359:
360: Iterator employees = pm.createExtent(PCEmployee.class, true)
361: .iterator();
362: while (employees.hasNext()) {
363: PCEmployee emp = (PCEmployee) employees.next();
364:
365: PCDepartment dept = (PCDepartment) PCEmployee.reflect(emp,
366: "department");
367: assertNotNull(dept);
368:
369: PCCompany company = (PCCompany) PCDepartment.reflect(dept,
370: "company");
371: assertNotNull(company);
372:
373: PCAddress addr = (PCAddress) PCCompany.reflect(company,
374: "address");
375: assertNotNull(addr);
376:
377: PCCountry country = (PCCountry) PCAddress.reflect(addr,
378: "country");
379: assertNull(country);
380: }
381: }
382:
383: /**
384: * Gets a Compnay object by getObjectById() method as opposed to query.
385: * The active fetch groups should bring in the multi-valued relationships.
386: * The address->country relationship can be reached in two alternate
387: * paths -- one as company->address->country and the other is
388: * company->department->employee->address->country.
389: * Though active fetch groups allow both the paths -- the max fetch depth
390: * is set such that the shorter path is taken but not the longer one.
391: * Hence the company's address->country should be loaded but not the
392: * employee's.
393: */
394: public void testRelationTraversalWithCompanyAsRoot() {
395: OpenJPAEntityManager pm = getPM();
396: FetchPlan plan = pm.getFetchPlan();
397:
398: plan.setMaxFetchDepth(2);
399: plan.addFetchGroup("company.departments");
400: plan.addFetchGroup("company.address");
401: plan.addFetchGroup("department.employees");
402: plan.addFetchGroup("person.address");
403: plan.addFetchGroup("address.country");
404:
405: PCCompany company = (PCCompany) pm.find(PCCompany.class,
406: _rootCompanyId);
407: Set departments = (Set) PCCompany.reflect(company,
408: "departments");
409: assertNotNull("department is null", departments);
410: assertEquals("exp. depart size is not 2", 2, departments.size());
411: PCDepartment dept = (PCDepartment) departments.iterator()
412: .next();
413: assertNotNull("dept is null", dept);
414: Set employees = (Set) PCDepartment.reflect(dept, "employees");
415: assertNotNull("employees is null", employees);
416: assertEquals(1, employees.size());
417: PCEmployee emp = (PCEmployee) employees.iterator().next();
418: assertNotNull("emp is not null", emp);
419: PCAddress eaddr = (PCAddress) PCPerson.reflect(emp, "address");
420: PCAddress caddr = (PCAddress) PCCompany.reflect(company,
421: "address");
422: assertNull("eaddr is not null", eaddr);
423: assertNotNull("caddr is null", caddr);
424: PCCountry country = (PCCountry) PCAddress.reflect(caddr,
425: "country");
426: assertNotNull("country is null", country);
427: }
428:
429: /**
430: * Same as above but the root compnay instance is detached.
431: */
432: public void testDetachedRelationTraversalWithCompanyAsRoot() {
433: OpenJPAEntityManager pm = getPM();
434: FetchPlan plan = pm.getFetchPlan();
435: pm.getFetchPlan().setMaxFetchDepth(2);
436: plan.addFetchGroup("company.departments");
437: plan.addFetchGroup("company.address");
438: plan.addFetchGroup("department.employees");
439: plan.addFetchGroup("person.address");
440: plan.addFetchGroup("address.country");
441:
442: PCCompany company1 = (PCCompany) pm.find(PCCompany.class,
443: _rootCompanyId);
444:
445: PCCompany company = (PCCompany) pm.detach(company1);
446: assertTrue("company is equal company1", company != company1);
447: Set departments = (Set) PCCompany.reflect(company,
448: "departments");
449: assertNotNull("department is null", departments);
450: assertEquals("department size is not 2", 2, departments.size());
451: PCDepartment dept = (PCDepartment) departments.iterator()
452: .next();
453: assertNotNull("dept is null", dept);
454: Set employees = (Set) PCDepartment.reflect(dept, "employees");
455: assertNotNull("employee is null", employees);
456: assertEquals("employees size not 1", 1, employees.size());
457: PCEmployee emp = (PCEmployee) employees.iterator().next();
458: assertNotNull("emp is null", emp);
459: PCAddress eaddr = (PCAddress) PCPerson.reflect(emp, "address");
460: PCAddress caddr = (PCAddress) PCCompany.reflect(company,
461: "address");
462: assertNull("eaddr is not null", eaddr);
463: assertNotNull("caddr is null", caddr);
464: PCCountry country = (PCCountry) PCAddress.reflect(caddr,
465: "country");
466: assertNotNull("country is null", country);
467: }
468:
469: public void testDefaultFetchGroup() {
470: OpenJPAEntityManager pm = getPM();
471:
472: String squery = "SELECT DISTINCT o FROM PCEmployee o WHERE o.name = 'Employee1'";
473: OpenJPAQuery q = pm.createQuery(squery);
474:
475: //FIXME jthomas
476: PCEmployee person = (PCEmployee) q.getSingleResult();
477: assertEquals("Exp. String is not employee1", "Employee1",
478: PCPerson.reflect(person, "name"));
479: }
480:
481: public void testDefaultFetchGroupExistsByDefault() {
482: OpenJPAEntityManager pm = getPM();
483: assertTrue("pm does not contain default fetchplan", pm
484: .getFetchPlan().getFetchGroups().contains(
485: FetchPlan.GROUP_DEFAULT));
486: }
487:
488: public void testDefaultFetchGroupCanBeRemoved() {
489: OpenJPAEntityManager pm = getPM();
490: assertTrue("does not contain default fetchplan", pm
491: .getFetchPlan().getFetchGroups().contains(
492: FetchPlan.GROUP_DEFAULT));
493:
494: pm.getFetchPlan().removeFetchGroup(FetchPlan.GROUP_DEFAULT);
495: assertFalse("does contain default fetchplan", pm.getFetchPlan()
496: .getFetchGroups().contains(FetchPlan.GROUP_DEFAULT));
497:
498: OpenJPAEntityManager pm2 = getPM();
499: assertTrue("pm2 does not contain default fetchplan", pm2
500: .getFetchPlan().getFetchGroups().contains(
501: FetchPlan.GROUP_DEFAULT));
502: }
503:
504: public void tearDown() throws Exception {
505: super .tearDown();
506: }
507:
508: private void clearTestData() throws Exception {
509: // OpenJPAEntityManagerFactory pmf =(OpenJPAEntityManagerFactory) getEmf();
510: // OpenJPAConfiguration conf=pmf.getConfiguration();
511: //
512: // Class.forName(pmf.getConfiguration().getConnection2DriverName());
513: // String url=conf.getConnection2URL();
514: // String user=conf.getConnection2UserName();
515: // String pass=conf.getConnection2Password();
516: //
517: // Connection con = DriverManager.getConnection(
518: // url,
519: // user,
520: // pass);
521: // con.setAutoCommit(true);
522: // con.prepareStatement("DELETE FROM PCDIRECTORY").executeUpdate();
523: // con.prepareStatement("DELETE FROM PCFILE").executeUpdate();
524: // con.prepareStatement("DELETE FROM PCPERSON").executeUpdate();
525: // con.prepareStatement("DELETE FROM PCDEPARTMENT").executeUpdate();
526: // con.prepareStatement("DELETE FROM PCCOMPANY").executeUpdate();
527: // con.prepareStatement("DELETE FROM PCADDRESS").executeUpdate();
528: // con.prepareStatement("DELETE FROM PCCOUNTRY").executeUpdate();
529:
530: deleteAll(PCDirectory.class);
531: deleteAll(PCFile.class);
532: deleteAll(PCPerson.class);
533: deleteAll(PCDepartment.class);
534: deleteAll(PCCompany.class);
535: deleteAll(PCAddress.class);
536: deleteAll(PCCountry.class);
537: deleteAll(PCEmployee.class);
538: }
539: }
|