1 package org.kuali.rice.krad.data.jpa;
2
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.assertNotNull;
5 import static org.junit.Assert.assertNull;
6 import static org.junit.Assert.assertTrue;
7 import static org.junit.Assert.fail;
8
9 import java.util.AbstractMap;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.TreeMap;
15
16 import javax.sql.DataSource;
17
18 import org.apache.commons.collections.CollectionUtils;
19 import org.apache.commons.lang.RandomStringUtils;
20 import org.apache.log4j.Logger;
21 import org.junit.Before;
22 import org.junit.Test;
23 import org.kuali.rice.core.api.criteria.OrderByField;
24 import org.kuali.rice.core.api.criteria.OrderDirection;
25 import org.kuali.rice.core.api.criteria.Predicate;
26 import org.kuali.rice.core.api.criteria.PredicateFactory;
27 import org.kuali.rice.core.api.criteria.QueryByCriteria;
28 import org.kuali.rice.core.api.criteria.QueryResults;
29 import org.kuali.rice.krad.data.CompoundKey;
30 import org.kuali.rice.krad.data.DataObjectWrapper;
31 import org.kuali.rice.krad.data.KradDataServiceLocator;
32 import org.kuali.rice.krad.data.PersistenceOption;
33 import org.kuali.rice.krad.data.platform.MaxValueIncrementerFactory;
34 import org.kuali.rice.krad.data.provider.PersistenceProvider;
35 import org.kuali.rice.krad.test.KRADTestCase;
36 import org.kuali.rice.krad.test.document.bo.Account;
37 import org.kuali.rice.krad.test.document.bo.AccountExtension;
38 import org.kuali.rice.krad.test.document.bo.AccountType;
39 import org.kuali.rice.krad.test.document.bo.SimpleAccount;
40 import org.kuali.rice.krad.test.document.bo.SimpleAccountExtension;
41 import org.kuali.rice.test.BaselineTestCase;
42 import org.kuali.rice.test.TestHarnessServiceLocator;
43 import org.springframework.dao.InvalidDataAccessApiUsageException;
44 import org.springframework.transaction.UnexpectedRollbackException;
45
46
47
48
49
50 @BaselineTestCase.BaselineMode(BaselineTestCase.Mode.CLEAR_DB)
51 public class JpaPersistenceProviderTest extends KRADTestCase {
52
53
54
55
56 protected PersistenceProvider provider;
57
58
59
60
61 @Before
62 public void setup() {
63 provider = getPersistenceProvider();
64 }
65
66
67
68
69 protected QueryByCriteria queryFor(Object a) {
70 return QueryByCriteria.Builder.andAttributes(a, Arrays.asList(getPropertiesForQuery())).build();
71 }
72
73
74
75
76 protected Object createLinkedTestObject() {
77 Object a = createTopLevelObject();
78 Object saved = getPersistenceProvider().save(a);
79
80 addLinkedReferences(saved);
81
82 return saved;
83 }
84
85
86
87
88 protected Object createUnlinkedTestObject() {
89 Object a = createTopLevelObject();
90 Object saved = getPersistenceProvider().save(a);
91
92 addUnlinkedReferences(saved);
93
94 return saved;
95 }
96
97
98
99
100 protected void assignPK(Object a) {
101 setTestObjectPK(a, getNextTestObjectId());
102 }
103
104
105
106
107 protected Map.Entry<Object, QueryByCriteria> createForQuery() {
108 Object a = createLinkedTestObject();
109 QueryByCriteria qbc = queryFor(a);
110 return new AbstractMap.SimpleImmutableEntry<Object, QueryByCriteria>(a, qbc);
111 }
112
113
114
115
116
117
118
119 protected Map.Entry<List<Object>, QueryByCriteria.Builder> createForQuery(int count) {
120 List<Object> objects = new ArrayList<Object>();
121 List<Predicate> predicates = new ArrayList<Predicate>(count);
122 for (int i = 0; i < count; i++) {
123 Object a = createLinkedTestObject();
124 objects.add(a);
125 predicates.add(queryFor(a).getPredicate());
126 }
127 QueryByCriteria.Builder qbc = QueryByCriteria.Builder.create();
128 qbc.setPredicates(PredicateFactory.or(predicates.toArray(new Predicate[count])));
129 return new AbstractMap.SimpleImmutableEntry<List<Object>, QueryByCriteria.Builder>(objects, qbc);
130 }
131
132 @Test
133 public void testSimpleSave() {
134 Object a = createTopLevelObject();
135 assertNull(getIdForLookup(a));
136
137 Object saved = provider.save(a);
138 assertNotNull(getIdForLookup(saved));
139 assertTestObjectEquals(a, saved);
140 }
141
142 @Test
143 public void testSaveLinkedSkipLinking() {
144 Object a = createLinkedTestObject();
145 Object id = getIdForLookup(a);
146
147 Object saved = provider.save(a);
148 assertTestObjectIdentityEquals(a, saved);
149
150 Object found = provider.find((Class<Object>)a.getClass(), id);
151 assertTestObjectIdentityEquals(a, found);
152 assertTestObjectIdentityEquals(saved, found);
153 }
154
155 @Test
156 public void testExtensionKeySaving() {
157 Account acct = new Account();
158 acct.setNumber("a1");
159 acct.setName("a1 name");
160 AccountExtension ext = new AccountExtension();
161 ext.setAccountTypeCode("EAX");
162 acct.setExtension(ext);
163
164 acct = KradDataServiceLocator.getDataObjectService().save(acct, PersistenceOption.FLUSH);
165 assertNotNull( "extension object was null after save", acct.getExtension());
166 assertEquals( "extension object class incorrect", AccountExtension.class, acct.getExtension().getClass() );
167
168 ext = (AccountExtension) acct.getExtension();
169 assertEquals( "account type code incorrect after save", "EAX", ext.getAccountTypeCode() );
170 assertNotNull( "account object on extension not persisted", ext.getAccount() );
171 assertEquals( "account ID on extension not persisted", "a1", ext.getNumber() );
172
173 provider.find(Account.class, "a1");
174 assertNotNull( "extension object was null after reload", acct.getExtension());
175 assertEquals( "extension object class incorrect after reload", AccountExtension.class, acct.getExtension().getClass() );
176 ext = (AccountExtension) acct.getExtension();
177 assertEquals( "account type code incorrect after reload", "EAX", ext.getAccountTypeCode() );
178 }
179
180 @Test
181 public void testExistsSubQueryCriteria() {
182
183 Logger.getLogger(getClass()).info( "Adding Account" );
184 Account acct = new Account();
185 acct.setNumber("a1");
186 acct.setName("a1 name");
187 provider.save(acct, PersistenceOption.FLUSH);
188
189 Logger.getLogger(getClass()).info( "Testing Account Saved" );
190 acct = provider.find(Account.class, "a1");
191 assertNotNull( "a1 SimpleAccount missing", acct );
192
193
194
195
196
197 Predicate subquery = PredicateFactory.existsSubquery(AccountExtension.class.getName(), PredicateFactory.equalsProperty("number", null, "parent.number"));
198 QueryByCriteria q = QueryByCriteria.Builder.fromPredicates(subquery);
199 Logger.getLogger(getClass()).info( "Performing Lookup with Exists Query: " + q );
200 QueryResults<Account> results = provider.findMatching(Account.class, q);
201
202 assertNotNull( "Results should not have been null", results );
203 assertEquals( "Should have been no results in the default data", 0, results.getResults().size() );
204
205 Logger.getLogger(getClass()).info( "Building extension object for retest" );
206 AccountExtension ext = new AccountExtension();
207 ext.setAccount(acct);
208 ext.setAccountTypeCode("EAX");
209 provider.save(ext, PersistenceOption.FLUSH);
210
211 Logger.getLogger(getClass()).info( "Running query again to test results" );
212 results = provider.findMatching(Account.class, q);
213 assertNotNull( "Results should not have been null", results );
214 assertEquals( "We added an extension record, so there should have been one result", 1, results.getResults().size() );
215 }
216
217
218
219
220
221 @Test(expected=UnexpectedRollbackException.class)
222 public void testSaveUnlinkedSkipLinking() {
223 Object a = createUnlinkedTestObject();
224
225 provider.save(a);
226
227 fail("save should have resulted in an exception as references have not been linked correctly");
228 }
229
230
231 @Test
232 public void testFindMatching() {
233 Map.Entry<Object, QueryByCriteria> pair = createForQuery();
234
235 Object a = pair.getKey();
236 QueryByCriteria qbc = pair.getValue();
237
238 Object saved = provider.save(a);
239 assertTestObjectEquals(a, saved);
240
241 QueryResults<Object> found = provider.findMatching((Class<Object>)a.getClass(), qbc);
242 assertEquals(1, found.getResults().size());
243 assertTestObjectIdentityEquals(a, found.getResults().get(0));
244
245 provider.delete(found.getResults().get(0));
246
247 found = provider.findMatching((Class<Object>)a.getClass(), qbc);
248 assertEquals(0, found.getResults().size());
249 }
250
251
252
253
254 @Test(expected = InvalidDataAccessApiUsageException.class)
255 public void testFindMatchingNullCriteria() {
256 Map.Entry<Object, QueryByCriteria> pair = createForQuery();
257
258 Object a = pair.getKey();
259
260 provider.findMatching(a.getClass(), null);
261 }
262
263
264
265
266 @Test
267 public void testFindMatchingEmptyCriteria() {
268 Map.Entry<Object, QueryByCriteria> pair = createForQuery();
269
270 Object a = pair.getKey();
271
272 provider.findMatching(a.getClass(), QueryByCriteria.Builder.create().build());
273 }
274
275 @Test
276 public void testFindBySingleKey() {
277 Object a = createLinkedTestObject();
278
279 Object saved = provider.save(a);
280 assertTestObjectEquals(a, saved);
281 Object id = getIdForLookup(saved);
282
283 Object found = provider.find((Class<Object>)a.getClass(), id);
284 assertTestObjectIdentityEquals(a, found);
285
286 provider.delete(found);
287
288 assertNull(provider.find((Class<Object>)a.getClass(), id));
289 }
290
291 @Test
292 public void testFindByCompoundKey() {
293 Object a = createLinkedTestObject();
294
295 Object saved = provider.save(a);
296 assertTestObjectEquals(a, saved);
297
298 Map<String, Object> keys = new TreeMap<String, Object>();
299 DataObjectWrapper<Object> wrap = KradDataServiceLocator.getDataObjectService().wrap(saved);
300 for (String propertyName : getPropertiesForQuery()) {
301 keys.put(propertyName, wrap.getPropertyValue(propertyName));
302 }
303 CompoundKey id = new CompoundKey(keys);
304
305 Object found = provider.find((Class<Object>)a.getClass(), id);
306 assertTestObjectIdentityEquals(a, found);
307
308 provider.delete(found);
309
310 assertNull(provider.find((Class<Object>)a.getClass(), id));
311 }
312
313 @Test
314 public void testFindMatchingOrderBy() {
315
316 Map.Entry<List<Object>, QueryByCriteria.Builder> fixture = createForQuery(10);
317 List<Object> objects = fixture.getKey();
318 for (Object a: objects) {
319 provider.save(a);
320 }
321
322
323 QueryByCriteria.Builder query = fixture.getValue();
324
325 OrderByField.Builder nameOrderBy = OrderByField.Builder.create();
326 OrderByField.Builder amIdOrderBy = OrderByField.Builder.create();
327
328 nameOrderBy.setFieldName("number");
329 nameOrderBy.setOrderDirection(OrderDirection.ASCENDING);
330
331 amIdOrderBy.setFieldName("amId");
332 amIdOrderBy.setOrderDirection(OrderDirection.ASCENDING);
333
334 query.setOrderByFields(nameOrderBy.build(), amIdOrderBy.build());
335
336
337 List<SimpleAccount> ascOrder = provider.findMatching(SimpleAccount.class, query.build()).getResults();
338
339
340 nameOrderBy.setOrderDirection(OrderDirection.DESCENDING);
341 amIdOrderBy.setOrderDirection(OrderDirection.DESCENDING);
342 query.setOrderByFields(nameOrderBy.build(), amIdOrderBy.build());
343 List<SimpleAccount> descOrder = provider.findMatching(SimpleAccount.class, query.build()).getResults();
344
345 assertEquals(ascOrder.size(), descOrder.size());
346
347
348 if (!CollectionUtils.isEmpty(ascOrder)) {
349 for (int idx = 0; idx<ascOrder.size();idx++) {
350 assertTestObjectIdentityEquals(ascOrder.get(idx), descOrder.get((ascOrder.size() - 1) - idx));
351 }
352 }
353
354 }
355
356 @Test
357 public void testFindWithResultsWindow() {
358
359 List<SimpleAccount> acctList = provider.findMatching(SimpleAccount.class, QueryByCriteria.Builder.create().build()).getResults();
360 if (CollectionUtils.isEmpty(acctList)) {
361 for (SimpleAccount acct : acctList) {
362 provider.delete(acct);
363 }
364 }
365
366
367 Map.Entry<List<Object>, QueryByCriteria.Builder> fixture = createForQuery(10);
368 List<Object> objects = fixture.getKey();
369 for (Object a: objects) {
370 provider.save(a);
371 }
372
373
374 QueryByCriteria.Builder query = fixture.getValue();
375
376
377 OrderByField.Builder orderBy = OrderByField.Builder.create();
378 orderBy.setFieldName("number");
379 orderBy.setOrderDirection(OrderDirection.ASCENDING);
380 query.setOrderByFields(orderBy.build());
381
382
383 List<SimpleAccount> resultsAll = provider.findMatching(SimpleAccount.class, query.build()).getResults();
384
385
386 query.setStartAtIndex(2);
387 query.setMaxResults(5);
388 List<SimpleAccount> results = provider.findMatching(SimpleAccount.class, query.build()).getResults();
389
390 assertEquals(5, results.size());
391 assertTestObjectIdentityEquals(resultsAll.get(2), results.get(0));
392 assertTestObjectIdentityEquals(resultsAll.get(3), results.get(1));
393 assertTestObjectIdentityEquals(resultsAll.get(4), results.get(2));
394 assertTestObjectIdentityEquals(resultsAll.get(5), results.get(3));
395 assertTestObjectIdentityEquals(resultsAll.get(6), results.get(4));
396 }
397
398
399
400
401 @Test
402 public void testFindAll() {
403 Object a = createTopLevelObject();
404 QueryResults<Object> results = provider.findAll((Class<Object>)a.getClass());
405 assertEquals(0, results.getResults().size());
406
407 Object savedA = provider.save(a);
408 results = provider.findAll((Class<Object>)a.getClass());
409 assertEquals(1, results.getResults().size());
410
411 provider.delete(savedA);
412 results = provider.findAll((Class<Object>)a.getClass());
413 assertEquals(0, results.getResults().size());
414 }
415
416
417
418
419
420 @Test
421 public void testDeleteNonExistentEntity() {
422 Object a = createTopLevelObject();
423 assignPK(a);
424 Object id = getIdForLookup(a);
425
426 assertNull(provider.find((Class<Object>)a.getClass(), id));
427
428 provider.delete(a);
429
430 assertNull(provider.find((Class<Object>)a.getClass(), id));
431 }
432
433
434
435
436 @Test(expected=InvalidDataAccessApiUsageException.class)
437 public void testDeleteMatchingNullCriteria() {
438 provider.deleteMatching(SimpleAccount.class, null);
439 }
440
441
442
443
444 @Test(expected=InvalidDataAccessApiUsageException.class)
445 public void testDeleteMatchingEmptyCriteria() {
446 provider.deleteMatching(SimpleAccount.class, QueryByCriteria.Builder.create().build());
447 }
448
449
450
451
452 @Test
453 public void testDeleteMatchingNonExistentEntity() {
454 List<String> nameList = new ArrayList<String>();
455
456
457 Object a = createTopLevelObject();
458 assignPK(a);
459 nameList.add(((SimpleAccount)a).getName());
460 Object b = createTopLevelObject();
461 assignPK(b);
462 nameList.add(((SimpleAccount) b).getName());
463 Object c = createTopLevelObject();
464 assignPK(c);
465 nameList.add(((SimpleAccount)c).getName());
466
467
468 QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
469 builder.setPredicates(PredicateFactory.in("name", nameList));
470
471 QueryResults<Object> found = provider.findMatching((Class<Object>)a.getClass(), builder.build());
472 assertEquals(0, found.getResults().size());
473
474 provider.deleteMatching(a.getClass(), builder.build());
475
476 found = (provider.findMatching((Class<Object>)a.getClass(), builder.build()));
477 assertEquals(0, found.getResults().size());
478 }
479
480
481
482
483 @Test
484 public void testDeleteMatchingAllSavedEntities() {
485 List<String> nameList = new ArrayList<String>();
486
487
488 Object a = createTopLevelObject();
489 assignPK(a);
490 Object savedA = provider.save(a);
491 nameList.add(((SimpleAccount)savedA).getName());
492
493 Object b = createTopLevelObject();
494 assignPK(b);
495 Object savedB = provider.save(b);
496 nameList.add(((SimpleAccount) savedB).getName());
497
498 Object c = createTopLevelObject();
499 assignPK(c);
500 Object savedC = provider.save(c);
501
502
503 QueryResults<Object> found = provider.findAll((Class<Object>) savedA.getClass());
504 assertEquals(3, found.getResults().size());
505
506
507 QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
508 builder.setPredicates(PredicateFactory.in("name", nameList));
509 provider.deleteMatching(a.getClass(), builder.build());
510
511
512 found = provider.findAll((Class<Object>) savedA.getClass());
513 assertEquals(1, found.getResults().size());
514 Object lastObject = found.getResults().get(0);
515 assertEquals(((SimpleAccount) lastObject).getName(), ((SimpleAccount)savedC).getName());
516
517
518 nameList.clear();
519 nameList.add(((SimpleAccount)savedC).getName());
520
521
522 builder = QueryByCriteria.Builder.create();
523 builder.setPredicates(PredicateFactory.in("name", nameList));
524 provider.deleteMatching(a.getClass(), builder.build());
525
526
527 found = provider.findAll((Class<Object>) savedA.getClass());
528 assertEquals(0, found.getResults().size());
529 }
530
531
532
533
534 @Test
535 public void testDeleteAll() {
536 Object a = createTopLevelObject();
537 QueryResults<Object> results = provider.findAll((Class<Object>)a.getClass());
538 assertEquals(0, results.getResults().size());
539 provider.deleteAll(a.getClass());
540 results = provider.findAll((Class<Object>)a.getClass());
541 assertEquals(0, results.getResults().size());
542
543 Object savedA = provider.save(a);
544 Object b = createTopLevelObject();
545 provider.save(b);
546 results = provider.findAll((Class<Object>) a.getClass());
547 assertEquals(2, results.getResults().size());
548
549 provider.deleteAll(savedA.getClass());
550 results = provider.findAll((Class<Object>)a.getClass());
551 assertEquals(0, results.getResults().size());
552 }
553
554 @Test
555 public void testHandles() {
556 Object a = createTopLevelObject();
557 assertTrue(provider.handles(a.getClass()));
558 Class guaranteedNotToBeMappedClass = this.getClass();
559
560 }
561
562 protected Object createTopLevelObject() {
563 SimpleAccount a = new SimpleAccount();
564 String name = RandomStringUtils.randomAlphanumeric(10);
565 a.setName(name);
566 return a;
567 }
568
569 protected void addLinkedReferences(Object o) {
570 SimpleAccount a = (SimpleAccount)o;
571 addUnlinkedReferences(a);
572
573 SimpleAccountExtension e = (SimpleAccountExtension) a.getExtension();
574 e.setAccountTypeCode(e.getAccountType().getAccountTypeCode());
575 e.setAccount(a);
576 }
577
578 protected void addUnlinkedReferences(Object o) {
579 SimpleAccount a = (SimpleAccount)o;
580
581
582
583 SimpleAccountExtension extension = new SimpleAccountExtension();
584 AccountType at = new AccountType();
585 at.setName(RandomStringUtils.randomAlphanumeric(10));
586 at.setAccountTypeCode(RandomStringUtils.randomAlphanumeric(2));
587 extension.setAccountType(at);
588 a.setExtension(extension);
589 }
590
591 protected String[] getPropertiesForQuery() {
592 return new String[] { "number", "name" };
593 }
594
595 protected Object getIdForLookup(Object o) {
596 SimpleAccount a = (SimpleAccount)o;
597 return a.getNumber();
598 }
599
600 protected String getNextTestObjectId() {
601 DataSource dataSource = TestHarnessServiceLocator.getDataSource();
602 return MaxValueIncrementerFactory.getIncrementer(dataSource, "trvl_id_seq").nextStringValue();
603 }
604
605 protected void setTestObjectPK(Object o, Object key) {
606 SimpleAccount a = (SimpleAccount)o;
607 a.setNumber((String) key);
608 }
609
610 protected void assertTestObjectIdentityEquals(Object oExpected, Object oActual) {
611 SimpleAccount expected = (SimpleAccount)oExpected;
612 SimpleAccount actual = (SimpleAccount)oActual;
613 assertTestObjectEquals(expected, actual);
614 assertEquals(expected.getNumber(), actual.getNumber());
615 }
616
617 protected void assertTestObjectEquals(Object oExpected, Object oActual) {
618 SimpleAccount expected = (SimpleAccount)oExpected;
619 SimpleAccount actual = (SimpleAccount)oActual;
620 assertEquals(expected.getAmId(), actual.getAmId());
621 assertEquals(expected.getName(), actual.getName());
622 if (expected.getExtension() != null) {
623 SimpleAccountExtension e1 = (SimpleAccountExtension) expected.getExtension();
624 SimpleAccountExtension e2 = (SimpleAccountExtension) actual.getExtension();
625 assertEquals(e1.getAccount().getNumber(), e2.getAccount().getNumber());
626 assertEquals(e1.getAccountTypeCode(), e2.getAccountTypeCode());
627
628 if (e1.getAccountType() != null) {
629 AccountType at1 = e1.getAccountType();
630 AccountType at2 = e2.getAccountType();
631 assertEquals(at1.getName(), at2.getName());
632 assertEquals(at1.getAccountTypeCode(), at2.getAccountTypeCode());
633 }
634 }
635
636 }
637
638 protected PersistenceProvider getPersistenceProvider() {
639 return getKRADTestHarnessContext().getBean("kradTestJpaPersistenceProvider", PersistenceProvider.class);
640 }
641
642 }