001/**
002 * Copyright 2005-2015 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.data.provider;
017
018import static org.junit.Assert.assertEquals;
019import static org.junit.Assert.assertFalse;
020import static org.junit.Assert.assertNotNull;
021import static org.junit.Assert.assertNull;
022import static org.junit.Assert.assertTrue;
023import static org.junit.Assert.fail;
024import static org.mockito.Matchers.any;
025import static org.mockito.Mockito.atLeastOnce;
026import static org.mockito.Mockito.never;
027import static org.mockito.Mockito.verify;
028import static org.mockito.Mockito.when;
029
030import java.util.ArrayList;
031import java.util.Arrays;
032import java.util.Collection;
033import java.util.Collections;
034import java.util.List;
035import java.util.Map;
036
037import org.apache.commons.lang3.StringUtils;
038import org.junit.Before;
039import org.junit.Test;
040import org.junit.runner.RunWith;
041import org.kuali.rice.core.api.criteria.EqualPredicate;
042import org.kuali.rice.core.api.criteria.GenericQueryResults;
043import org.kuali.rice.core.api.criteria.GenericQueryResults.Builder;
044import org.kuali.rice.core.api.criteria.QueryByCriteria;
045import org.kuali.rice.krad.data.DataObjectService;
046import org.kuali.rice.krad.data.DataObjectWrapper;
047import org.kuali.rice.krad.data.MaterializeOption;
048import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
049import org.kuali.rice.krad.data.metadata.MetadataChild;
050import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeRelationshipImpl;
051import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionImpl;
052import org.kuali.rice.krad.data.metadata.impl.DataObjectRelationshipImpl;
053import org.kuali.rice.krad.data.provider.impl.DataObjectWrapperBase;
054import org.kuali.rice.krad.data.util.ReferenceLinker;
055import org.mockito.Mock;
056import org.mockito.invocation.InvocationOnMock;
057import org.mockito.runners.MockitoJUnitRunner;
058import org.mockito.stubbing.Answer;
059import org.springframework.beans.NullValueInNestedPathException;
060
061@RunWith(MockitoJUnitRunner.class)
062public class DataObjectWrapperBaseTest {
063
064    @Mock private DataObjectService dataObjectService;
065        @Mock
066        private DataObjectMetadata dataObjectMetadata;
067        @Mock
068        private DataObjectMetadata dataObject2Metadata;
069        @Mock
070        private DataObjectMetadata dataObject3Metadata;
071        @Mock
072        private DataObjectMetadata dataObject4Metadata;
073        @Mock
074        private DataObjectMetadata dataObject5Metadata;
075    @Mock private ReferenceLinker referenceLinker;
076
077    private DataObject dataObject;
078    private DataObject2 dataObject2;
079        private DataObject4 dataObject4;
080        private DataObjectWrapperBase<DataObject> wrap;
081
082        @SuppressWarnings({ "unchecked", "rawtypes" })
083        protected void setUpDataObjectMetadataMocks() {
084                when(dataObjectService.supports(DataObject.class)).thenReturn(true);
085                when(dataObjectMetadata.getType()).thenReturn((Class) DataObject.class);
086                when(dataObjectMetadata.getPrimaryKeyAttributeNames()).thenReturn(Collections.singletonList("id"));
087
088                // M:1 relationship, lazy-loaded, not part of parent object tree
089                DataObjectRelationshipImpl dataObject2Relationship = new DataObjectRelationshipImpl();
090                dataObject2Relationship.setName("dataObject2");
091                dataObject2Relationship.setRelatedType(DataObject2.class);
092                dataObject2Relationship.setSavedWithParent(false);
093                dataObject2Relationship.setLoadedAtParentLoadTime(false);
094                dataObject2Relationship.setLoadedDynamicallyUponUse(true);
095                dataObject2Relationship.setAttributeRelationships((List) Collections
096                                .singletonList(new DataObjectAttributeRelationshipImpl("dataObject2sKey", "one")));
097
098                when(dataObjectMetadata.getRelationship("dataObject2")).thenReturn(dataObject2Relationship);
099
100                // M:1 relationship, eager-loaded, not part of parent object tree
101                DataObjectRelationshipImpl eagerDataObject2Relationship = new DataObjectRelationshipImpl();
102                eagerDataObject2Relationship.setName("eagerDataObject2");
103                eagerDataObject2Relationship.setRelatedType(DataObject2.class);
104                eagerDataObject2Relationship.setSavedWithParent(false);
105                eagerDataObject2Relationship.setLoadedAtParentLoadTime(true);
106                eagerDataObject2Relationship.setLoadedDynamicallyUponUse(false);
107                eagerDataObject2Relationship.setAttributeRelationships((List) Collections
108                                .singletonList(new DataObjectAttributeRelationshipImpl("dataObject2sKey", "one")));
109
110                when(dataObjectMetadata.getRelationship("eagerDataObject2")).thenReturn(eagerDataObject2Relationship);
111
112                when(dataObjectMetadata.getRelationships()).thenReturn(
113                                (List) Arrays.asList(dataObject2Relationship, eagerDataObject2Relationship));
114
115                // 1:M relationship, lazy-loaded, saved with parent
116                DataObjectCollectionImpl dataObject3Relationship = new DataObjectCollectionImpl();
117                dataObject3Relationship.setName("dataObject3s");
118                dataObject3Relationship.setRelatedType(DataObject3.class);
119                dataObject3Relationship.setSavedWithParent(true);
120                dataObject3Relationship.setLoadedAtParentLoadTime(false);
121                dataObject3Relationship.setLoadedDynamicallyUponUse(true);
122                dataObject3Relationship.setAttributeRelationships((List) Collections
123                                .singletonList(new DataObjectAttributeRelationshipImpl("id", "parentId")));
124
125                when(dataObjectMetadata.getCollections()).thenReturn((List) Collections.singletonList(dataObject3Relationship));
126                when(dataObjectMetadata.getCollection("dataObject3s")).thenReturn(dataObject3Relationship);
127    }
128    
129        @SuppressWarnings({ "unchecked", "rawtypes" })
130        protected void setUpDataObject2MetadataMocks() {
131                when(dataObjectService.supports(DataObject2.class)).thenReturn(true);
132                when(dataObject2Metadata.getType()).thenReturn((Class) DataObject2.class);
133                when(dataObject2Metadata.getPrimaryKeyAttributeNames()).thenReturn(Collections.singletonList("one"));
134
135                // M:1 relationship, lazy-loaded, not part of parent object tree
136                DataObjectRelationshipImpl dataObject4Relationship = new DataObjectRelationshipImpl();
137                dataObject4Relationship.setName("dataObject4");
138                dataObject4Relationship.setRelatedType(DataObject4.class);
139                dataObject4Relationship.setSavedWithParent(false);
140                dataObject4Relationship.setLoadedAtParentLoadTime(false);
141                dataObject4Relationship.setLoadedDynamicallyUponUse(true);
142                dataObject4Relationship.setAttributeRelationships((List) Collections
143                                .singletonList(new DataObjectAttributeRelationshipImpl("two", "pk")));
144
145                when(dataObject2Metadata.getRelationship("dataObject4")).thenReturn(dataObject4Relationship);
146                when(dataObject2Metadata.getRelationships()).thenReturn((List) Arrays.asList(dataObject4Relationship));
147        }
148
149        @SuppressWarnings({ "unchecked", "rawtypes" })
150        protected void setUpDataObject3MetadataMocks() {
151                when(dataObjectService.supports(DataObject3.class)).thenReturn(true);
152                when(dataObject3Metadata.getType()).thenReturn((Class) DataObject3.class);
153                when(dataObject3Metadata.getPrimaryKeyAttributeNames()).thenReturn(Arrays.asList("parentId", "id"));
154
155                // M:1 relationship, lazy-loaded, not part of parent object tree
156                DataObjectRelationshipImpl dataObject2Relationship = new DataObjectRelationshipImpl();
157                dataObject2Relationship.setName("dataObject2");
158                dataObject2Relationship.setRelatedType(DataObject2.class);
159                dataObject2Relationship.setSavedWithParent(false);
160                dataObject2Relationship.setLoadedAtParentLoadTime(false);
161                dataObject2Relationship.setLoadedDynamicallyUponUse(true);
162                dataObject2Relationship.setAttributeRelationships((List) Collections
163                                .singletonList(new DataObjectAttributeRelationshipImpl("world", "one")));
164
165                when(dataObject3Metadata.getRelationship("dataObject2")).thenReturn(dataObject2Relationship);
166                when(dataObject3Metadata.getRelationships()).thenReturn((List) Arrays.asList(dataObject2Relationship));
167        }
168
169        @SuppressWarnings({ "unchecked", "rawtypes" })
170        protected void setUpDataObject4MetadataMocks() {
171                when(dataObjectService.supports(DataObject4.class)).thenReturn(true);
172                when(dataObject4Metadata.getType()).thenReturn((Class) DataObject4.class);
173                when(dataObject4Metadata.getPrimaryKeyAttributeNames()).thenReturn(Collections.singletonList("pk"));
174        }
175
176        @SuppressWarnings({ "unchecked", "rawtypes" })
177        protected void setUpDataObject5MetadataMocks() {
178                when(dataObjectService.supports(DataObject.class)).thenReturn(true);
179                when(dataObject5Metadata.getType()).thenReturn((Class) DataObject.class);
180                when(dataObject5Metadata.getPrimaryKeyAttributeNames()).thenReturn(Collections.singletonList("id"));
181
182                // M:1 relationship, lazy-loaded, not part of parent object tree
183                DataObjectRelationshipImpl dataObject2Relationship = new DataObjectRelationshipImpl();
184                dataObject2Relationship.setName("dataObject2");
185                dataObject2Relationship.setRelatedType(DataObject2.class);
186                dataObject2Relationship.setSavedWithParent(false);
187                dataObject2Relationship.setLoadedAtParentLoadTime(false);
188                dataObject2Relationship.setLoadedDynamicallyUponUse(true);
189                dataObject2Relationship.setAttributeRelationships((List) Collections
190                                .singletonList(new DataObjectAttributeRelationshipImpl("dataObject2sKey", null)));
191
192                when(dataObject5Metadata.getRelationship("dataObject2")).thenReturn(dataObject2Relationship);
193
194                // M:1 relationship, eager-loaded, not part of parent object tree
195                DataObjectRelationshipImpl eagerDataObject2Relationship = new DataObjectRelationshipImpl();
196                eagerDataObject2Relationship.setName("eagerDataObject2");
197                eagerDataObject2Relationship.setRelatedType(DataObject2.class);
198                eagerDataObject2Relationship.setSavedWithParent(false);
199                eagerDataObject2Relationship.setLoadedAtParentLoadTime(true);
200                eagerDataObject2Relationship.setLoadedDynamicallyUponUse(false);
201                eagerDataObject2Relationship.setAttributeRelationships((List) Collections
202                                .singletonList(new DataObjectAttributeRelationshipImpl("dataObject2sKey", "one")));
203
204                when(dataObject5Metadata.getRelationship("eagerDataObject2")).thenReturn(eagerDataObject2Relationship);
205
206                when(dataObject5Metadata.getRelationships()).thenReturn(
207                                (List) Arrays.asList(dataObject2Relationship, eagerDataObject2Relationship));
208
209                // 1:M relationship, lazy-loaded, saved with parent
210                DataObjectCollectionImpl dataObject3Relationship = new DataObjectCollectionImpl();
211                dataObject3Relationship.setName("dataObject3s");
212                dataObject3Relationship.setRelatedType(DataObject3.class);
213                dataObject3Relationship.setSavedWithParent(true);
214                dataObject3Relationship.setLoadedAtParentLoadTime(false);
215                dataObject3Relationship.setLoadedDynamicallyUponUse(true);
216                dataObject3Relationship.setAttributeRelationships((List) Collections
217                                .singletonList(new DataObjectAttributeRelationshipImpl("id", "parentId")));
218
219                when(dataObject5Metadata.getCollections()).thenReturn((List) Collections.singletonList(dataObject3Relationship));
220                when(dataObject5Metadata.getCollection("dataObject3s")).thenReturn(dataObject3Relationship);
221        }
222
223    protected void configureMocks() {
224                setUpDataObjectMetadataMocks();
225                setUpDataObject2MetadataMocks();
226                setUpDataObject3MetadataMocks();
227                setUpDataObject4MetadataMocks();
228                setUpDataObject5MetadataMocks();
229
230                when(dataObjectService.findMatching(any(Class.class), any(QueryByCriteria.class))).thenAnswer(new Answer() {
231                        @Override
232                        public Object answer(InvocationOnMock invocation) {
233                                Class<?> dataObjectType = (Class<?>) invocation.getArguments()[0];
234                                QueryByCriteria criteria = (QueryByCriteria) invocation.getArguments()[1];
235                                if (DataObject3.class.isAssignableFrom(dataObjectType)) {
236                                        if (criteria.getPredicate() instanceof EqualPredicate
237                                                        && ((EqualPredicate) criteria.getPredicate()).getPropertyPath().equals("parentId")
238                                                        && ((EqualPredicate) criteria.getPredicate()).getValue().getValue().equals("1")) {
239                                                Builder builder = GenericQueryResults.Builder.create();
240                                                builder.setResults(Arrays.asList(
241                                                                new DataObject3("1", "C1", "hello", "world")
242                                                                , new DataObject3("1", "C2", "howdy", "Westeros")));
243                                                builder.setTotalRowCount(2);
244                                                return builder.build();
245                                        }
246                                }
247
248                                // return a completely empty object if unknown
249                                return GenericQueryResults.Builder.create().build();
250                        }
251                });
252
253                when(dataObjectService.find(any(Class.class), any())).thenAnswer(new Answer() {
254                        @Override
255                        public Object answer(InvocationOnMock invocation) {
256                                Class<?> dataObjectType = (Class<?>) invocation.getArguments()[0];
257                                Object primaryKey = invocation.getArguments()[1];
258                                if (DataObject2.class.isAssignableFrom(dataObjectType)) {
259                                        if (primaryKey instanceof String && StringUtils.equals((String) primaryKey, "one")) {
260                                                return dataObject2;
261                                        }
262                                } else if (DataObject4.class.isAssignableFrom(dataObjectType)) {
263                                        if (primaryKey instanceof String && StringUtils.equals((String) primaryKey, "two")) {
264                                                return dataObject4;
265                                        }
266                                }
267
268                                return null;
269                        }
270                });
271
272        // make it so that DataObjectService returns a proper wrap when asked
273                when(dataObjectService.wrap(any())).thenAnswer(new Answer() {
274            @Override
275                        public Object answer(InvocationOnMock invocation) {
276                                Object object = invocation.getArguments()[0];
277                                if (object instanceof DataObject) {
278                                        return new DataObjectWrapperImpl<DataObject>((DataObject) object, dataObjectMetadata,
279                                                        dataObjectService,
280                                                        referenceLinker);
281                                } else if (object instanceof DataObject2) {
282                                        return new DataObjectWrapperImpl<DataObject2>((DataObject2) object, dataObject2Metadata,
283                                                        dataObjectService,
284                                                        referenceLinker);
285                                } else if (object instanceof DataObject3) {
286                                        return new DataObjectWrapperImpl<DataObject3>((DataObject3) object, dataObject3Metadata,
287                                                        dataObjectService, referenceLinker);
288                                } else if (object instanceof DataObject4) {
289                                        return new DataObjectWrapperImpl<DataObject4>((DataObject4) object, dataObject4Metadata,
290                                                        dataObjectService,
291                                                        referenceLinker);
292                                }
293                                return new DataObjectWrapperImpl<Object>(object, null, dataObjectService, referenceLinker);
294            }
295        });
296    }
297    
298    @Before
299    public void setup() throws Exception {
300                dataObject = new DataObject("1", "FieldOne", 2, "one");
301                dataObject2 = new DataObject2("one", "two");
302                dataObject.setDataObject2(dataObject2);
303                dataObject4 = new DataObject4("two", "some other value");
304                wrap = new DataObjectWrapperImpl<DataObject>(dataObject, dataObjectMetadata, dataObjectService,
305                                referenceLinker);
306
307                configureMocks();
308    }
309
310    static final class DataObjectWrapperImpl<T> extends DataObjectWrapperBase<T> {
311        private DataObjectWrapperImpl(T dataObject, DataObjectMetadata metadata, DataObjectService dataObjectService,
312                ReferenceLinker referenceLinker) {
313            super(dataObject, metadata, dataObjectService, referenceLinker);
314        }
315    }
316
317        @Test
318        public void testGetForeignKeyAttributeMap_NoNullChildAttributeName(){
319                wrap = new DataObjectWrapperImpl<DataObject>(dataObject, dataObject5Metadata, dataObjectService, referenceLinker);
320                final Map<String, Object> result = wrap.getForeignKeyAttributeMap("dataObject2");
321                assertTrue(result.get((String)null)==null);
322        }
323
324    @Test
325    public void testGetType() {
326        assertEquals(DataObject.class, wrap.getWrappedClass());
327    }
328
329    @Test
330    public void testGetMetadata() {
331                assertEquals(dataObjectMetadata, wrap.getMetadata());
332    }
333
334    @Test
335    public void testGetWrappedInstance() {
336        assertEquals(dataObject, wrap.getWrappedInstance());
337    }
338
339    @Test
340    public void testGetPrimaryKeyValues() {
341        Map<String, Object> primaryKeyValues = wrap.getPrimaryKeyValues();
342        assertEquals(1, primaryKeyValues.size());
343        assertTrue(primaryKeyValues.containsKey("id"));
344        assertEquals("1", primaryKeyValues.get("id"));
345
346                assertEquals("Mismatch on DataObject2's PKs", Collections.singletonMap("one", "one"),
347                                dataObjectService.wrap(dataObject2).getPrimaryKeyValues());
348    }
349
350    @Test
351    public void testEqualsByPrimaryKey() {
352        // first check that it's equal to itself
353        assertTrue(wrap.equalsByPrimaryKey(dataObject));
354
355        // now create one with an equal primary key but different values for non-pk fields, should be euqual
356                assertTrue(wrap.equalsByPrimaryKey(new DataObject("1", "blah", 500, "one")));
357
358        // now create one with a different primary key, should not be equal
359                assertFalse(wrap.equalsByPrimaryKey(new DataObject("2", "FieldOne", 2, "one")));
360
361        // let's do some null checking
362        assertFalse(wrap.equalsByPrimaryKey(null));
363
364        // verify what happens when primary key is null on object being compared
365                assertFalse(wrap.equalsByPrimaryKey(new DataObject(null, null, -1, "one")));
366
367    }
368
369    @Test
370    public void testGetPropertyType_Nested() {
371        assertEquals(DataObject2.class, wrap.getPropertyType("dataObject2"));
372        assertEquals(String.class, wrap.getPropertyType("dataObject2.one"));
373        assertEquals(String.class, wrap.getPropertyType("dataObject2.two"));
374    }
375
376    @Test
377    public void testGetPropertyValueNullSafe() {
378                DataObject dataObject = new DataObject("a", "b", 3, "one");
379                DataObjectWrapper<DataObject> wrap = new DataObjectWrapperImpl<DataObject>(dataObject, dataObjectMetadata,
380                                dataObjectService,
381                referenceLinker);
382        assertNull(wrap.getPropertyValue("dataObject2"));
383
384        //wrap.setPropertyValue("dataObject2.dataObject3", new DataObject3());
385
386        // assert that a NullValueInNestedPathException is thrown
387        try {
388            wrap.getPropertyValue("dataObject2.dataObject3");
389            fail("NullValueInNestedPathException should have been thrown");
390        } catch (NullValueInNestedPathException e) {
391            // this should be thrown!
392        }
393
394        // now do a null-safe check
395        assertNull(wrap.getPropertyValueNullSafe("dataObject2.dataObject3"));
396
397    }
398
399    @Test
400    public void testGetPropertyType_Collection() {
401        // setup stuff
402                DataObject dataObject = new DataObject("a", "b", 3, "one");
403        DataObject3 do3_1 = new DataObject3();
404        do3_1.setHello("hi");
405        do3_1.setWorld("Earth");
406        DataObject3 do3_2 = new DataObject3();
407        do3_2.setHello("howdy");
408        do3_2.setWorld("Westeros");
409        dataObject.getDataObject3s().add(do3_1);
410        dataObject.getDataObject3s().add(do3_2);
411
412        // now check through a collection
413                DataObjectWrapper<DataObject> wrap = new DataObjectWrapperImpl<DataObject>(dataObject, dataObjectMetadata,
414                                dataObjectService,
415                referenceLinker);
416        Class<?> type = wrap.getPropertyType("dataObject3s[0].hello");
417        assertEquals(String.class, type);
418        type = wrap.getPropertyType("dataObject3s[1].world");
419        assertEquals(String.class, type);
420        type = wrap.getPropertyType("dataObject3s[2].world");
421        // should be null because we have nothing at this index
422        assertNull(type);
423    }
424
425        @Test
426        public void testMaterializeOptionMatch_Default() {
427                Collection<MetadataChild> childRelationships = wrap.getChildrenMatchingOptions();
428                assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
429                assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 1, childRelationships.size());
430                assertEquals("getChildrenMatchingOptions() returned wrong type of relationship",
431                                DataObjectRelationshipImpl.class, childRelationships.iterator().next().getClass());
432                assertEquals("getChildrenMatchingOptions() relationship was for wrong property", "dataObject2",
433                                childRelationships.iterator().next().getName());
434                assertEquals("getChildrenMatchingOptions() relationship was for wrong data type", DataObject2.class,
435                                childRelationships.iterator().next().getRelatedType());
436        }
437
438        @Test
439        public void testMaterializeOptionMatch_WithEager() {
440                Collection<MetadataChild> childRelationships = wrap
441                                .getChildrenMatchingOptions(MaterializeOption.INCLUDE_EAGER_REFS);
442                assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
443                assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 2, childRelationships.size());
444        }
445
446        @Test
447        public void testMaterializeOptionMatch_CollectionsOnly_NonUpdatable() {
448                Collection<MetadataChild> childRelationships = wrap.getChildrenMatchingOptions(MaterializeOption.COLLECTIONS);
449                assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
450                assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 0, childRelationships.size());
451        }
452
453        @Test
454        public void testMaterializeOptionMatch_CollectionsOnly_Updatable() {
455                Collection<MetadataChild> childRelationships = wrap.getChildrenMatchingOptions(MaterializeOption.COLLECTIONS,
456                                MaterializeOption.UPDATE_UPDATABLE_REFS);
457                assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
458                assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 1, childRelationships.size());
459                assertEquals("getChildrenMatchingOptions() returned wrong type of relationship",
460                                DataObjectCollectionImpl.class, childRelationships.iterator().next().getClass());
461                assertEquals("getChildrenMatchingOptions() relationship was for wrong property", "dataObject3s",
462                                childRelationships.iterator().next().getName());
463                assertEquals("getChildrenMatchingOptions() relationship was for wrong data type", DataObject3.class,
464                                childRelationships.iterator().next().getRelatedType());
465        }
466
467        @Test
468        public void testMaterializeOptionMatch_Updatable() {
469                Collection<MetadataChild> childRelationships = wrap
470                                .getChildrenMatchingOptions(MaterializeOption.UPDATE_UPDATABLE_REFS);
471                assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
472                assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 2, childRelationships.size());
473        }
474
475        @Test
476        public void testMaterialize_Default() {
477                // nulling out reference so it can be reloaded
478                dataObject.setDataObject2(null);
479
480                wrap.materializeReferencedObjects();
481                verify(dataObjectService).supports(DataObject2.class);
482                verify(dataObjectService, never()).supports(DataObject4.class);
483
484                verify(dataObjectService).find(DataObject2.class, "one");
485                assertNotNull("dataObject2 should have been loaded", dataObject.getDataObject2());
486        }
487
488        @Test
489        public void testMaterialize_Recursive() {
490                // nulling out reference so it can be reloaded
491                dataObject.setDataObject2(null);
492
493                wrap.materializeReferencedObjectsToDepth(2);
494                verify(dataObjectService, atLeastOnce()).supports(DataObject2.class);
495                verify(dataObjectService).find(DataObject2.class, "one");
496                verify(dataObjectService, atLeastOnce()).supports(DataObject4.class);
497                verify(dataObjectService).find(DataObject4.class, "two");
498
499                assertNotNull("dataObject2 should have been loaded", dataObject.getDataObject2());
500                assertNotNull("dataObject2.dataObject4 should have been loaded", dataObject.getDataObject2().getDataObject4());
501        }
502
503        @Test
504        public void testMaterialize_Recursive_WithCollections() {
505                // nulling out reference so it can be reloaded
506                dataObject.setDataObject2(null);
507                dataObject.setDataObject3s(null);
508
509                wrap.materializeReferencedObjectsToDepth(2, MaterializeOption.UPDATE_UPDATABLE_REFS);
510                verify(dataObjectService, atLeastOnce()).supports(DataObject2.class);
511                verify(dataObjectService, atLeastOnce()).supports(DataObject3.class);
512                verify(dataObjectService, atLeastOnce()).supports(DataObject4.class);
513
514                verify(dataObjectService).find(DataObject2.class, "one");
515                verify(dataObjectService).find(DataObject4.class, "two");
516
517                assertNotNull("dataObject2 should have been loaded", dataObject.getDataObject2());
518                assertNotNull("dataObject2.dataObject4 should have been loaded", dataObject.getDataObject2().getDataObject4());
519
520                verify(dataObjectService).findMatching(DataObject3.class,
521                                QueryByCriteria.Builder.andAttributes(Collections.singletonMap("parentId", "1")).build());
522
523                assertNotNull("The list of DataObject3 should not have been nulled out", dataObject.getDataObject3s());
524                assertEquals("The list of DataObject3 should have had records", 2, dataObject.getDataObject3s().size());
525
526                // Confirm that it attempted to load each of the child objects of the child records in the collection
527                verify(dataObjectService).find(DataObject2.class, "world");
528                verify(dataObjectService).find(DataObject2.class, "Westeros");
529        }
530
531        @Test
532        public void testMaterialize_InvalidCode_DontNullIt() {
533                assertNotNull("dataObject2 should not be null at start of test", dataObject.getDataObject2());
534
535                // setting the foreign key to an invalid value
536                dataObject.setDataObject2sKey("SOMETHING_INVALID");
537
538                wrap.materializeReferencedObjects();
539                verify(dataObjectService).supports(DataObject2.class);
540                verify(dataObjectService).find(DataObject2.class, "SOMETHING_INVALID");
541                assertNotNull("dataObject2 should not have been nulled out", dataObject.getDataObject2());
542                assertEquals("The object should be the original, with the originals PK", "one", dataObject.getDataObject2()
543                                .getOne());
544        }
545
546        @Test
547        public void testMaterialize_InvalidCode_PleaseNullIt() {
548                assertNotNull("dataObject2 should not be null at start of test", dataObject.getDataObject2());
549
550                // setting the foreign key to an invalid value
551                dataObject.setDataObject2sKey("SOMETHING_INVALID");
552
553                wrap.materializeReferencedObjects(MaterializeOption.NULL_INVALID_REFS);
554                verify(dataObjectService).supports(DataObject2.class);
555                verify(dataObjectService).find(DataObject2.class, "SOMETHING_INVALID");
556                assertNull("dataObject2 should have been nulled out", dataObject.getDataObject2());
557        }
558
559        @Test
560        public void testMaterialize_UpdateUpdatable() {
561                wrap.materializeReferencedObjects(MaterializeOption.UPDATE_UPDATABLE_REFS);
562                verify(dataObjectService).supports(DataObject2.class);
563                verify(dataObjectService).supports(DataObject3.class);
564                verify(dataObjectService).find(DataObject2.class, "one");
565                verify(dataObjectService).findMatching(DataObject3.class,
566                                QueryByCriteria.Builder.andAttributes(Collections.singletonMap("parentId", "1")).build());
567                assertNotNull("The list of DataObject3 should not have been nulled out", dataObject.getDataObject3s());
568        }
569
570    public static final class DataObject {
571
572        private String id;
573        private String fieldOne;
574        private Integer fieldTwo;
575                private String dataObject2sKey;
576        private DataObject2 dataObject2;
577                private DataObject2 eagerDataObject2;
578
579        private List<DataObject3> dataObject3s;
580
581                DataObject(String id, String fieldOne, Integer fieldTwo, String dataObject2sKey) {
582            this.id = id;
583            this.fieldOne = fieldOne;
584            this.fieldTwo = fieldTwo;
585                        this.dataObject2sKey = dataObject2sKey;
586            this.dataObject3s = new ArrayList<DataObject3>();
587        }
588
589        public String getId() {
590            return id;
591        }
592
593        public void setId(String id) {
594            this.id = id;
595        }
596
597        public String getFieldOne() {
598            return fieldOne;
599        }
600
601        public void setFieldOne(String fieldOne) {
602            this.fieldOne = fieldOne;
603        }
604
605        public Integer getFieldTwo() {
606            return fieldTwo;
607        }
608
609        public void setFieldTwo(Integer fieldTwo) {
610            this.fieldTwo = fieldTwo;
611        }
612
613        public DataObject2 getDataObject2() {
614            return dataObject2;
615        }
616
617        public void setDataObject2(DataObject2 dataObject2) {
618            this.dataObject2 = dataObject2;
619        }
620
621        public List<DataObject3> getDataObject3s() {
622            return dataObject3s;
623        }
624
625        public void setDataObject3s(List<DataObject3> dataObject3s) {
626            this.dataObject3s = dataObject3s;
627        }
628
629                public String getDataObject2sKey() {
630                        return dataObject2sKey;
631                }
632
633                public void setDataObject2sKey(String dataObject2sKey) {
634                        this.dataObject2sKey = dataObject2sKey;
635                }
636
637                public DataObject2 getEagerDataObject2() {
638                        return eagerDataObject2;
639                }
640
641                public void setEagerDataObject2(DataObject2 eagerDataObject2) {
642                        this.eagerDataObject2 = eagerDataObject2;
643                }
644    }
645
646    public static final class DataObject2 {
647        private String one;
648                private String two; // FK for data object 4
649                private DataObject4 dataObject4;
650
651        public DataObject2() {}
652
653        public DataObject2(String one, String two) {
654            this.one = one;
655            this.two = two;
656        }
657
658        public String getOne() {
659            return one;
660        }
661
662        public void setOne(String one) {
663            this.one = one;
664        }
665
666        public String getTwo() {
667            return two;
668        }
669
670        public void setTwo(String two) {
671            this.two = two;
672        }
673
674                public DataObject4 getDataObject4() {
675                        return dataObject4;
676        }
677
678                public void setDataObject4(DataObject4 dataObject3) {
679                        this.dataObject4 = dataObject3;
680        }
681    }
682
683        public static final class DataObject4 {
684                private String pk;
685                private String notPk;
686
687                public DataObject4(String pk, String notPk) {
688                        this.pk = pk;
689                        this.notPk = notPk;
690                }
691
692                public String getPk() {
693                        return pk;
694                }
695
696                public void setPk(String pk) {
697                        this.pk = pk;
698                }
699
700                public String getNotPk() {
701                        return notPk;
702                }
703
704                public void setNotPk(String notPk) {
705                        this.notPk = notPk;
706                }
707        }
708
709    public static final class DataObject3 {
710
711                private String parentId;
712                private String id;
713        private String hello;
714                private String world; // FK for data object 2
715                private DataObject2 dataObject2;
716
717                public DataObject3() {
718                }
719
720                public DataObject3(String parentId, String id, String hello, String world) {
721                        super();
722                        this.parentId = parentId;
723                        this.id = id;
724                        this.hello = hello;
725                        this.world = world;
726                }
727
728                public String getHello() {
729            return hello;
730        }
731
732        public void setHello(String hello) {
733            this.hello = hello;
734        }
735
736        public String getWorld() {
737            return world;
738        }
739
740        public void setWorld(String world) {
741            this.world = world;
742        }
743
744                public String getParentId() {
745                        return parentId;
746                }
747
748                public void setParentId(String parentId) {
749                        this.parentId = parentId;
750                }
751
752                public String getId() {
753                        return id;
754                }
755
756                public void setId(String id) {
757                        this.id = id;
758                }
759
760                public DataObject2 getDataObject2() {
761                        return dataObject2;
762                }
763
764                public void setDataObject2(DataObject2 dataObject2) {
765                        this.dataObject2 = dataObject2;
766                }
767
768    }
769}