View Javadoc
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   * Tests JPAPersistenceProvider
48   */
49  // avoid wrapping test in rollback since JPA requires transaction boundary to flush
50  @BaselineTestCase.BaselineMode(BaselineTestCase.Mode.CLEAR_DB)
51  public class JpaPersistenceProviderTest extends KRADTestCase {
52  
53      /**
54       * The PersistenceProvider being tested
55       */
56      protected PersistenceProvider provider;
57  
58      /**
59       * Obtains PersistenceProvider.
60       */
61      @Before
62      public void setup() {
63          provider = getPersistenceProvider();
64      }
65  
66      /**
67       * Derives a QueryByCriteria for a test object
68       */
69      protected QueryByCriteria queryFor(Object a) {
70          return QueryByCriteria.Builder.andAttributes(a, Arrays.asList(getPropertiesForQuery())).build();
71      }
72  
73      /**
74       * Creates an unsaved test object
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       * Creates an unsaved, unlinked test object
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       * Assigns the next generated primary key value to the test object
99       */
100     protected void assignPK(Object a) {
101         setTestObjectPK(a, getNextTestObjectId());
102     }
103 
104     /**
105      * Creates a test object and generates a matching query for it
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      * Generates a batch of test objects and returns a single query that will select them all.
115      * The order of the returned list of test objects should match the order of the results returned
116      * by the underlying platform (i.e., if they are returned in a sorted order, then the test object
117      * list should be sorted).
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          * Testing query of form:
194          *
195          * SELECT * FROM SimpleAccount WHERE EXISTS ( SELECT 'x' FROM SimpleAccountExtension WHERE SimpleAccountExtension.number = SimpleAccount.number )
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     // EclipseLink consumes the underlying exception itself and explicitly rolls back the transaction
218     // resulting in just an opaque UnexpectedRollbackException coming out of Spring
219     // (underlying exception is never translated by the PersistenceExceptionTranslator)
220     // Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'ACCT_TYPE' cannot be null
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      * Ensures an IllegalArgumentException is thrown when a null value is passed in as the second parameter.
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      * Ensures no errors or exceptions occur when the second parameter's predicate value is null
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         // create our sample data
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         // get the query for our created sample data
323         QueryByCriteria.Builder query = fixture.getValue();
324         // specify the order
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         // get all created objects, ordered by number column ascending order
337         List<SimpleAccount> ascOrder = provider.findMatching(SimpleAccount.class, query.build()).getResults();
338 
339         // get all created objects, ordered by number column descending order
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         // ensure the two lists are exact opposites
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         // get all existing Simple Accounts and delete them so we have a fresh start
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         // now create our sample data
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         // get the query for our created sample data
374         QueryByCriteria.Builder query = fixture.getValue();
375 
376         // specify the order
377         OrderByField.Builder orderBy = OrderByField.Builder.create();
378         orderBy.setFieldName("number");
379         orderBy.setOrderDirection(OrderDirection.ASCENDING);
380         query.setOrderByFields(orderBy.build());
381 
382         // get all created objects, ordered by number column
383         List<SimpleAccount> resultsAll = provider.findMatching(SimpleAccount.class, query.build()).getResults();
384 
385         // now create the window, also ordered by number column
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      * Exercises the findAll method to ensure expected behavior
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      * Tests that deletion of a non-existent detached object does not result in a save of the object
418      * via merge.
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      * Test delete matching with null criteria, should throw an exception
435      */
436     @Test(expected=InvalidDataAccessApiUsageException.class)
437     public void testDeleteMatchingNullCriteria() {
438         provider.deleteMatching(SimpleAccount.class, null);
439     }
440 
441     /**
442      * Test delete matching with empty criteria, should throw an exception
443      */
444     @Test(expected=InvalidDataAccessApiUsageException.class)
445     public void testDeleteMatchingEmptyCriteria() {
446         provider.deleteMatching(SimpleAccount.class, QueryByCriteria.Builder.create().build());
447     }
448 
449     /**
450      * Tests the deletion of non-existent detached objects.
451      */
452     @Test
453     public void testDeleteMatchingNonExistentEntity() {
454         List<String> nameList = new ArrayList<String>();
455 
456         // build three objects to test with
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         // build the criteria for these three objects
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      * Tests the deletion of saved objects.
482      */
483     @Test
484     public void testDeleteMatchingAllSavedEntities() {
485         List<String> nameList = new ArrayList<String>();
486 
487         // build and save three objects to test with
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         // did all three objects get saved?
503         QueryResults<Object> found = provider.findAll((Class<Object>) savedA.getClass());
504         assertEquals(3, found.getResults().size());
505 
506         // now delete part of the saved objects
507         QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
508         builder.setPredicates(PredicateFactory.in("name", nameList));
509         provider.deleteMatching(a.getClass(), builder.build());
510 
511         // were the two objects deleted
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         // clear the list and add the last object
518         nameList.clear();
519         nameList.add(((SimpleAccount)savedC).getName());
520 
521         // now delete the last object.
522         builder = QueryByCriteria.Builder.create();
523         builder.setPredicates(PredicateFactory.in("name", nameList));
524         provider.deleteMatching(a.getClass(), builder.build());
525 
526         // were all objects deleted?
527         found = provider.findAll((Class<Object>) savedA.getClass());
528         assertEquals(0, found.getResults().size());
529     }
530 
531     /**
532      * Exercises the deleteAll method to ensure expected behavior
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         //  assertFalse(provider.handles(guaranteedNotToBeMappedClass));
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         //a.getAccountManager().setAmId(Long.parseLong(a.getNumber()));
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         //AccountManager am = new AccountManager();
581         //am.setUserName(RandomStringUtils.randomAlphanumeric(10));
582         //a.setAccountManager(am);
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 }