View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.data.provider;
17  
18  import static org.junit.Assert.assertEquals;
19  import static org.junit.Assert.assertFalse;
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.assertNull;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  import static org.mockito.Matchers.any;
25  import static org.mockito.Mockito.atLeastOnce;
26  import static org.mockito.Mockito.never;
27  import static org.mockito.Mockito.verify;
28  import static org.mockito.Mockito.when;
29  
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collection;
33  import java.util.Collections;
34  import java.util.List;
35  import java.util.Map;
36  
37  import org.apache.commons.lang3.StringUtils;
38  import org.junit.Before;
39  import org.junit.Test;
40  import org.junit.runner.RunWith;
41  import org.kuali.rice.core.api.criteria.EqualPredicate;
42  import org.kuali.rice.core.api.criteria.GenericQueryResults;
43  import org.kuali.rice.core.api.criteria.GenericQueryResults.Builder;
44  import org.kuali.rice.core.api.criteria.QueryByCriteria;
45  import org.kuali.rice.krad.data.DataObjectService;
46  import org.kuali.rice.krad.data.DataObjectWrapper;
47  import org.kuali.rice.krad.data.MaterializeOption;
48  import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
49  import org.kuali.rice.krad.data.metadata.MetadataChild;
50  import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeRelationshipImpl;
51  import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionImpl;
52  import org.kuali.rice.krad.data.metadata.impl.DataObjectRelationshipImpl;
53  import org.kuali.rice.krad.data.provider.impl.DataObjectWrapperBase;
54  import org.kuali.rice.krad.data.util.ReferenceLinker;
55  import org.mockito.Mock;
56  import org.mockito.invocation.InvocationOnMock;
57  import org.mockito.runners.MockitoJUnitRunner;
58  import org.mockito.stubbing.Answer;
59  import org.springframework.beans.NullValueInNestedPathException;
60  
61  @RunWith(MockitoJUnitRunner.class)
62  public class DataObjectWrapperBaseTest {
63  
64      @Mock private DataObjectService dataObjectService;
65  	@Mock
66  	private DataObjectMetadata dataObjectMetadata;
67  	@Mock
68  	private DataObjectMetadata dataObject2Metadata;
69  	@Mock
70  	private DataObjectMetadata dataObject3Metadata;
71  	@Mock
72  	private DataObjectMetadata dataObject4Metadata;
73  	@Mock
74  	private DataObjectMetadata dataObject5Metadata;
75      @Mock private ReferenceLinker referenceLinker;
76  
77      private DataObject dataObject;
78      private DataObject2 dataObject2;
79  	private DataObject4 dataObject4;
80  	private DataObjectWrapperBase<DataObject> wrap;
81  
82  	@SuppressWarnings({ "unchecked", "rawtypes" })
83  	protected void setUpDataObjectMetadataMocks() {
84  		when(dataObjectService.supports(DataObject.class)).thenReturn(true);
85  		when(dataObjectMetadata.getType()).thenReturn((Class) DataObject.class);
86  		when(dataObjectMetadata.getPrimaryKeyAttributeNames()).thenReturn(Collections.singletonList("id"));
87  
88  		// M:1 relationship, lazy-loaded, not part of parent object tree
89  		DataObjectRelationshipImpl dataObject2Relationship = new DataObjectRelationshipImpl();
90  		dataObject2Relationship.setName("dataObject2");
91  		dataObject2Relationship.setRelatedType(DataObject2.class);
92  		dataObject2Relationship.setSavedWithParent(false);
93  		dataObject2Relationship.setLoadedAtParentLoadTime(false);
94  		dataObject2Relationship.setLoadedDynamicallyUponUse(true);
95  		dataObject2Relationship.setAttributeRelationships((List) Collections
96  				.singletonList(new DataObjectAttributeRelationshipImpl("dataObject2sKey", "one")));
97  
98  		when(dataObjectMetadata.getRelationship("dataObject2")).thenReturn(dataObject2Relationship);
99  
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 }