View Javadoc
1   /**
2    * Copyright 2005-2014 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 private ReferenceLinker referenceLinker;
74  
75      private DataObject dataObject;
76      private DataObject2 dataObject2;
77  	private DataObject4 dataObject4;
78  	private DataObjectWrapperBase<DataObject> wrap;
79  
80  	@SuppressWarnings({ "unchecked", "rawtypes" })
81  	protected void setUpDataObjectMetadataMocks() {
82  		when(dataObjectService.supports(DataObject.class)).thenReturn(true);
83  		when(dataObjectMetadata.getType()).thenReturn((Class) DataObject.class);
84  		when(dataObjectMetadata.getPrimaryKeyAttributeNames()).thenReturn(Collections.singletonList("id"));
85  
86  		// M:1 relationship, lazy-loaded, not part of parent object tree
87  		DataObjectRelationshipImpl dataObject2Relationship = new DataObjectRelationshipImpl();
88  		dataObject2Relationship.setName("dataObject2");
89  		dataObject2Relationship.setRelatedType(DataObject2.class);
90  		dataObject2Relationship.setSavedWithParent(false);
91  		dataObject2Relationship.setLoadedAtParentLoadTime(false);
92  		dataObject2Relationship.setLoadedDynamicallyUponUse(true);
93  		dataObject2Relationship.setAttributeRelationships((List) Collections
94  				.singletonList(new DataObjectAttributeRelationshipImpl("dataObject2sKey", "one")));
95  
96  		when(dataObjectMetadata.getRelationship("dataObject2")).thenReturn(dataObject2Relationship);
97  
98  		// M:1 relationship, eager-loaded, not part of parent object tree
99  		DataObjectRelationshipImpl eagerDataObject2Relationship = new DataObjectRelationshipImpl();
100 		eagerDataObject2Relationship.setName("eagerDataObject2");
101 		eagerDataObject2Relationship.setRelatedType(DataObject2.class);
102 		eagerDataObject2Relationship.setSavedWithParent(false);
103 		eagerDataObject2Relationship.setLoadedAtParentLoadTime(true);
104 		eagerDataObject2Relationship.setLoadedDynamicallyUponUse(false);
105 		eagerDataObject2Relationship.setAttributeRelationships((List) Collections
106 				.singletonList(new DataObjectAttributeRelationshipImpl("dataObject2sKey", "one")));
107 
108 		when(dataObjectMetadata.getRelationship("eagerDataObject2")).thenReturn(eagerDataObject2Relationship);
109 
110 		when(dataObjectMetadata.getRelationships()).thenReturn(
111 				(List) Arrays.asList(dataObject2Relationship, eagerDataObject2Relationship));
112 
113 		// 1:M relationship, lazy-loaded, saved with parent
114 		DataObjectCollectionImpl dataObject3Relationship = new DataObjectCollectionImpl();
115 		dataObject3Relationship.setName("dataObject3s");
116 		dataObject3Relationship.setRelatedType(DataObject3.class);
117 		dataObject3Relationship.setSavedWithParent(true);
118 		dataObject3Relationship.setLoadedAtParentLoadTime(false);
119 		dataObject3Relationship.setLoadedDynamicallyUponUse(true);
120 		dataObject3Relationship.setAttributeRelationships((List) Collections
121 				.singletonList(new DataObjectAttributeRelationshipImpl("id", "parentId")));
122 
123 		when(dataObjectMetadata.getCollections()).thenReturn((List) Collections.singletonList(dataObject3Relationship));
124 		when(dataObjectMetadata.getCollection("dataObject3s")).thenReturn(dataObject3Relationship);
125     }
126     
127 	@SuppressWarnings({ "unchecked", "rawtypes" })
128 	protected void setUpDataObject2MetadataMocks() {
129 		when(dataObjectService.supports(DataObject2.class)).thenReturn(true);
130 		when(dataObject2Metadata.getType()).thenReturn((Class) DataObject2.class);
131 		when(dataObject2Metadata.getPrimaryKeyAttributeNames()).thenReturn(Collections.singletonList("one"));
132 
133 		// M:1 relationship, lazy-loaded, not part of parent object tree
134 		DataObjectRelationshipImpl dataObject4Relationship = new DataObjectRelationshipImpl();
135 		dataObject4Relationship.setName("dataObject4");
136 		dataObject4Relationship.setRelatedType(DataObject4.class);
137 		dataObject4Relationship.setSavedWithParent(false);
138 		dataObject4Relationship.setLoadedAtParentLoadTime(false);
139 		dataObject4Relationship.setLoadedDynamicallyUponUse(true);
140 		dataObject4Relationship.setAttributeRelationships((List) Collections
141 				.singletonList(new DataObjectAttributeRelationshipImpl("two", "pk")));
142 
143 		when(dataObject2Metadata.getRelationship("dataObject4")).thenReturn(dataObject4Relationship);
144 		when(dataObject2Metadata.getRelationships()).thenReturn((List) Arrays.asList(dataObject4Relationship));
145 	}
146 
147 	@SuppressWarnings({ "unchecked", "rawtypes" })
148 	protected void setUpDataObject3MetadataMocks() {
149 		when(dataObjectService.supports(DataObject3.class)).thenReturn(true);
150 		when(dataObject3Metadata.getType()).thenReturn((Class) DataObject3.class);
151 		when(dataObject3Metadata.getPrimaryKeyAttributeNames()).thenReturn(Arrays.asList("parentId", "id"));
152 
153 		// M:1 relationship, lazy-loaded, not part of parent object tree
154 		DataObjectRelationshipImpl dataObject2Relationship = new DataObjectRelationshipImpl();
155 		dataObject2Relationship.setName("dataObject2");
156 		dataObject2Relationship.setRelatedType(DataObject2.class);
157 		dataObject2Relationship.setSavedWithParent(false);
158 		dataObject2Relationship.setLoadedAtParentLoadTime(false);
159 		dataObject2Relationship.setLoadedDynamicallyUponUse(true);
160 		dataObject2Relationship.setAttributeRelationships((List) Collections
161 				.singletonList(new DataObjectAttributeRelationshipImpl("world", "one")));
162 
163 		when(dataObject3Metadata.getRelationship("dataObject2")).thenReturn(dataObject2Relationship);
164 		when(dataObject3Metadata.getRelationships()).thenReturn((List) Arrays.asList(dataObject2Relationship));
165 	}
166 
167 	@SuppressWarnings({ "unchecked", "rawtypes" })
168 	protected void setUpDataObject4MetadataMocks() {
169 		when(dataObjectService.supports(DataObject4.class)).thenReturn(true);
170 		when(dataObject4Metadata.getType()).thenReturn((Class) DataObject4.class);
171 		when(dataObject4Metadata.getPrimaryKeyAttributeNames()).thenReturn(Collections.singletonList("pk"));
172 	}
173 
174     protected void configureMocks() {
175 		setUpDataObjectMetadataMocks();
176 		setUpDataObject2MetadataMocks();
177 		setUpDataObject3MetadataMocks();
178 		setUpDataObject4MetadataMocks();
179 
180 		when(dataObjectService.findMatching(any(Class.class), any(QueryByCriteria.class))).thenAnswer(new Answer() {
181 			@Override
182 			public Object answer(InvocationOnMock invocation) {
183 				Class<?> dataObjectType = (Class<?>) invocation.getArguments()[0];
184 				QueryByCriteria criteria = (QueryByCriteria) invocation.getArguments()[1];
185 				if (DataObject3.class.isAssignableFrom(dataObjectType)) {
186 					if (criteria.getPredicate() instanceof EqualPredicate
187 							&& ((EqualPredicate) criteria.getPredicate()).getPropertyPath().equals("parentId")
188 							&& ((EqualPredicate) criteria.getPredicate()).getValue().getValue().equals("1")) {
189 						Builder builder = GenericQueryResults.Builder.create();
190 						builder.setResults(Arrays.asList(
191 								new DataObject3("1", "C1", "hello", "world")
192 								, new DataObject3("1", "C2", "howdy", "Westeros")));
193 						builder.setTotalRowCount(2);
194 						return builder.build();
195 					}
196 				}
197 
198 				// return a completely empty object if unknown
199 				return GenericQueryResults.Builder.create().build();
200 			}
201 		});
202 
203 		when(dataObjectService.find(any(Class.class), any())).thenAnswer(new Answer() {
204 			@Override
205 			public Object answer(InvocationOnMock invocation) {
206 				Class<?> dataObjectType = (Class<?>) invocation.getArguments()[0];
207 				Object primaryKey = invocation.getArguments()[1];
208 				if (DataObject2.class.isAssignableFrom(dataObjectType)) {
209 					if (primaryKey instanceof String && StringUtils.equals((String) primaryKey, "one")) {
210 						return dataObject2;
211 					}
212 				} else if (DataObject4.class.isAssignableFrom(dataObjectType)) {
213 					if (primaryKey instanceof String && StringUtils.equals((String) primaryKey, "two")) {
214 						return dataObject4;
215 					}
216 				}
217 
218 				return null;
219 			}
220 		});
221 
222         // make it so that DataObjectService returns a proper wrap when asked
223 		when(dataObjectService.wrap(any())).thenAnswer(new Answer() {
224             @Override
225 			public Object answer(InvocationOnMock invocation) {
226 				Object object = invocation.getArguments()[0];
227 				if (object instanceof DataObject) {
228 					return new DataObjectWrapperImpl<DataObject>((DataObject) object, dataObjectMetadata,
229 							dataObjectService,
230 							referenceLinker);
231 				} else if (object instanceof DataObject2) {
232 					return new DataObjectWrapperImpl<DataObject2>((DataObject2) object, dataObject2Metadata,
233 							dataObjectService,
234 							referenceLinker);
235 				} else if (object instanceof DataObject3) {
236 					return new DataObjectWrapperImpl<DataObject3>((DataObject3) object, dataObject3Metadata,
237 							dataObjectService, referenceLinker);
238 				} else if (object instanceof DataObject4) {
239 					return new DataObjectWrapperImpl<DataObject4>((DataObject4) object, dataObject4Metadata,
240 							dataObjectService,
241 							referenceLinker);
242 				}
243 				return new DataObjectWrapperImpl<Object>(object, null, dataObjectService, referenceLinker);
244             }
245         });
246     }
247     
248     @Before
249     public void setup() throws Exception {
250 		dataObject = new DataObject("1", "FieldOne", 2, "one");
251 		dataObject2 = new DataObject2("one", "two");
252 		dataObject.setDataObject2(dataObject2);
253 		dataObject4 = new DataObject4("two", "some other value");
254 		wrap = new DataObjectWrapperImpl<DataObject>(dataObject, dataObjectMetadata, dataObjectService,
255 				referenceLinker);
256 
257 		configureMocks();
258     }
259 
260     static final class DataObjectWrapperImpl<T> extends DataObjectWrapperBase<T> {
261         private DataObjectWrapperImpl(T dataObject, DataObjectMetadata metadata, DataObjectService dataObjectService,
262                 ReferenceLinker referenceLinker) {
263             super(dataObject, metadata, dataObjectService, referenceLinker);
264         }
265     }
266 
267     @Test
268     public void testGetType() {
269         assertEquals(DataObject.class, wrap.getWrappedClass());
270     }
271 
272     @Test
273     public void testGetMetadata() {
274 		assertEquals(dataObjectMetadata, wrap.getMetadata());
275     }
276 
277     @Test
278     public void testGetWrappedInstance() {
279         assertEquals(dataObject, wrap.getWrappedInstance());
280     }
281 
282     @Test
283     public void testGetPrimaryKeyValues() {
284         Map<String, Object> primaryKeyValues = wrap.getPrimaryKeyValues();
285         assertEquals(1, primaryKeyValues.size());
286         assertTrue(primaryKeyValues.containsKey("id"));
287         assertEquals("1", primaryKeyValues.get("id"));
288 
289 		assertEquals("Mismatch on DataObject2's PKs", Collections.singletonMap("one", "one"),
290 				dataObjectService.wrap(dataObject2).getPrimaryKeyValues());
291     }
292 
293     @Test
294     public void testEqualsByPrimaryKey() {
295         // first check that it's equal to itself
296         assertTrue(wrap.equalsByPrimaryKey(dataObject));
297 
298         // now create one with an equal primary key but different values for non-pk fields, should be euqual
299 		assertTrue(wrap.equalsByPrimaryKey(new DataObject("1", "blah", 500, "one")));
300 
301         // now create one with a different primary key, should not be equal
302 		assertFalse(wrap.equalsByPrimaryKey(new DataObject("2", "FieldOne", 2, "one")));
303 
304         // let's do some null checking
305         assertFalse(wrap.equalsByPrimaryKey(null));
306 
307         // verify what happens when primary key is null on object being compared
308 		assertFalse(wrap.equalsByPrimaryKey(new DataObject(null, null, -1, "one")));
309 
310     }
311 
312     @Test
313     public void testGetPropertyType_Nested() {
314         assertEquals(DataObject2.class, wrap.getPropertyType("dataObject2"));
315         assertEquals(String.class, wrap.getPropertyType("dataObject2.one"));
316         assertEquals(String.class, wrap.getPropertyType("dataObject2.two"));
317     }
318 
319     @Test
320     public void testGetPropertyValueNullSafe() {
321 		DataObject dataObject = new DataObject("a", "b", 3, "one");
322 		DataObjectWrapper<DataObject> wrap = new DataObjectWrapperImpl<DataObject>(dataObject, dataObjectMetadata,
323 				dataObjectService,
324                 referenceLinker);
325         assertNull(wrap.getPropertyValue("dataObject2"));
326 
327         //wrap.setPropertyValue("dataObject2.dataObject3", new DataObject3());
328 
329         // assert that a NullValueInNestedPathException is thrown
330         try {
331             wrap.getPropertyValue("dataObject2.dataObject3");
332             fail("NullValueInNestedPathException should have been thrown");
333         } catch (NullValueInNestedPathException e) {
334             // this should be thrown!
335         }
336 
337         // now do a null-safe check
338         assertNull(wrap.getPropertyValueNullSafe("dataObject2.dataObject3"));
339 
340     }
341 
342     @Test
343     public void testGetPropertyType_Collection() {
344         // setup stuff
345 		DataObject dataObject = new DataObject("a", "b", 3, "one");
346         DataObject3 do3_1 = new DataObject3();
347         do3_1.setHello("hi");
348         do3_1.setWorld("Earth");
349         DataObject3 do3_2 = new DataObject3();
350         do3_2.setHello("howdy");
351         do3_2.setWorld("Westeros");
352         dataObject.getDataObject3s().add(do3_1);
353         dataObject.getDataObject3s().add(do3_2);
354 
355         // now check through a collection
356 		DataObjectWrapper<DataObject> wrap = new DataObjectWrapperImpl<DataObject>(dataObject, dataObjectMetadata,
357 				dataObjectService,
358                 referenceLinker);
359         Class<?> type = wrap.getPropertyType("dataObject3s[0].hello");
360         assertEquals(String.class, type);
361         type = wrap.getPropertyType("dataObject3s[1].world");
362         assertEquals(String.class, type);
363         type = wrap.getPropertyType("dataObject3s[2].world");
364         // should be null because we have nothing at this index
365         assertNull(type);
366     }
367 
368 	@Test
369 	public void testMaterializeOptionMatch_Default() {
370 		Collection<MetadataChild> childRelationships = wrap.getChildrenMatchingOptions();
371 		assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
372 		assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 1, childRelationships.size());
373 		assertEquals("getChildrenMatchingOptions() returned wrong type of relationship",
374 				DataObjectRelationshipImpl.class, childRelationships.iterator().next().getClass());
375 		assertEquals("getChildrenMatchingOptions() relationship was for wrong property", "dataObject2",
376 				childRelationships.iterator().next().getName());
377 		assertEquals("getChildrenMatchingOptions() relationship was for wrong data type", DataObject2.class,
378 				childRelationships.iterator().next().getRelatedType());
379 	}
380 
381 	@Test
382 	public void testMaterializeOptionMatch_WithEager() {
383 		Collection<MetadataChild> childRelationships = wrap
384 				.getChildrenMatchingOptions(MaterializeOption.INCLUDE_EAGER_REFS);
385 		assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
386 		assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 2, childRelationships.size());
387 	}
388 
389 	@Test
390 	public void testMaterializeOptionMatch_CollectionsOnly_NonUpdatable() {
391 		Collection<MetadataChild> childRelationships = wrap.getChildrenMatchingOptions(MaterializeOption.COLLECTIONS);
392 		assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
393 		assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 0, childRelationships.size());
394 	}
395 
396 	@Test
397 	public void testMaterializeOptionMatch_CollectionsOnly_Updatable() {
398 		Collection<MetadataChild> childRelationships = wrap.getChildrenMatchingOptions(MaterializeOption.COLLECTIONS,
399 				MaterializeOption.UPDATE_UPDATABLE_REFS);
400 		assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
401 		assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 1, childRelationships.size());
402 		assertEquals("getChildrenMatchingOptions() returned wrong type of relationship",
403 				DataObjectCollectionImpl.class, childRelationships.iterator().next().getClass());
404 		assertEquals("getChildrenMatchingOptions() relationship was for wrong property", "dataObject3s",
405 				childRelationships.iterator().next().getName());
406 		assertEquals("getChildrenMatchingOptions() relationship was for wrong data type", DataObject3.class,
407 				childRelationships.iterator().next().getRelatedType());
408 	}
409 
410 	@Test
411 	public void testMaterializeOptionMatch_Updatable() {
412 		Collection<MetadataChild> childRelationships = wrap
413 				.getChildrenMatchingOptions(MaterializeOption.UPDATE_UPDATABLE_REFS);
414 		assertNotNull("getChildrenMatchingOptions() shoud not return null", childRelationships);
415 		assertEquals("getChildrenMatchingOptions() returned wrong number of rows", 2, childRelationships.size());
416 	}
417 
418 	@Test
419 	public void testMaterialize_Default() {
420 		// nulling out reference so it can be reloaded
421 		dataObject.setDataObject2(null);
422 
423 		wrap.materializeReferencedObjects();
424 		verify(dataObjectService).supports(DataObject2.class);
425 		verify(dataObjectService, never()).supports(DataObject4.class);
426 
427 		verify(dataObjectService).find(DataObject2.class, "one");
428 		assertNotNull("dataObject2 should have been loaded", dataObject.getDataObject2());
429 	}
430 
431 	@Test
432 	public void testMaterialize_Recursive() {
433 		// nulling out reference so it can be reloaded
434 		dataObject.setDataObject2(null);
435 
436 		wrap.materializeReferencedObjectsToDepth(2);
437 		verify(dataObjectService, atLeastOnce()).supports(DataObject2.class);
438 		verify(dataObjectService).find(DataObject2.class, "one");
439 		verify(dataObjectService, atLeastOnce()).supports(DataObject4.class);
440 		verify(dataObjectService).find(DataObject4.class, "two");
441 
442 		assertNotNull("dataObject2 should have been loaded", dataObject.getDataObject2());
443 		assertNotNull("dataObject2.dataObject4 should have been loaded", dataObject.getDataObject2().getDataObject4());
444 	}
445 
446 	@Test
447 	public void testMaterialize_Recursive_WithCollections() {
448 		// nulling out reference so it can be reloaded
449 		dataObject.setDataObject2(null);
450 		dataObject.setDataObject3s(null);
451 
452 		wrap.materializeReferencedObjectsToDepth(2, MaterializeOption.UPDATE_UPDATABLE_REFS);
453 		verify(dataObjectService, atLeastOnce()).supports(DataObject2.class);
454 		verify(dataObjectService, atLeastOnce()).supports(DataObject3.class);
455 		verify(dataObjectService, atLeastOnce()).supports(DataObject4.class);
456 
457 		verify(dataObjectService).find(DataObject2.class, "one");
458 		verify(dataObjectService).find(DataObject4.class, "two");
459 
460 		assertNotNull("dataObject2 should have been loaded", dataObject.getDataObject2());
461 		assertNotNull("dataObject2.dataObject4 should have been loaded", dataObject.getDataObject2().getDataObject4());
462 
463 		verify(dataObjectService).findMatching(DataObject3.class,
464 				QueryByCriteria.Builder.andAttributes(Collections.singletonMap("parentId", "1")).build());
465 
466 		assertNotNull("The list of DataObject3 should not have been nulled out", dataObject.getDataObject3s());
467 		assertEquals("The list of DataObject3 should have had records", 2, dataObject.getDataObject3s().size());
468 
469 		// Confirm that it attempted to load each of the child objects of the child records in the collection
470 		verify(dataObjectService).find(DataObject2.class, "world");
471 		verify(dataObjectService).find(DataObject2.class, "Westeros");
472 	}
473 
474 	@Test
475 	public void testMaterialize_InvalidCode_DontNullIt() {
476 		assertNotNull("dataObject2 should not be null at start of test", dataObject.getDataObject2());
477 
478 		// setting the foreign key to an invalid value
479 		dataObject.setDataObject2sKey("SOMETHING_INVALID");
480 
481 		wrap.materializeReferencedObjects();
482 		verify(dataObjectService).supports(DataObject2.class);
483 		verify(dataObjectService).find(DataObject2.class, "SOMETHING_INVALID");
484 		assertNotNull("dataObject2 should not have been nulled out", dataObject.getDataObject2());
485 		assertEquals("The object should be the original, with the originals PK", "one", dataObject.getDataObject2()
486 				.getOne());
487 	}
488 
489 	@Test
490 	public void testMaterialize_InvalidCode_PleaseNullIt() {
491 		assertNotNull("dataObject2 should not be null at start of test", dataObject.getDataObject2());
492 
493 		// setting the foreign key to an invalid value
494 		dataObject.setDataObject2sKey("SOMETHING_INVALID");
495 
496 		wrap.materializeReferencedObjects(MaterializeOption.NULL_INVALID_REFS);
497 		verify(dataObjectService).supports(DataObject2.class);
498 		verify(dataObjectService).find(DataObject2.class, "SOMETHING_INVALID");
499 		assertNull("dataObject2 should have been nulled out", dataObject.getDataObject2());
500 	}
501 
502 	@Test
503 	public void testMaterialize_UpdateUpdatable() {
504 		wrap.materializeReferencedObjects(MaterializeOption.UPDATE_UPDATABLE_REFS);
505 		verify(dataObjectService).supports(DataObject2.class);
506 		verify(dataObjectService).supports(DataObject3.class);
507 		verify(dataObjectService).find(DataObject2.class, "one");
508 		verify(dataObjectService).findMatching(DataObject3.class,
509 				QueryByCriteria.Builder.andAttributes(Collections.singletonMap("parentId", "1")).build());
510 		assertNotNull("The list of DataObject3 should not have been nulled out", dataObject.getDataObject3s());
511 	}
512 
513     public static final class DataObject {
514 
515         private String id;
516         private String fieldOne;
517         private Integer fieldTwo;
518 		private String dataObject2sKey;
519         private DataObject2 dataObject2;
520 		private DataObject2 eagerDataObject2;
521 
522         private List<DataObject3> dataObject3s;
523 
524 		DataObject(String id, String fieldOne, Integer fieldTwo, String dataObject2sKey) {
525             this.id = id;
526             this.fieldOne = fieldOne;
527             this.fieldTwo = fieldTwo;
528 			this.dataObject2sKey = dataObject2sKey;
529             this.dataObject3s = new ArrayList<DataObject3>();
530         }
531 
532         public String getId() {
533             return id;
534         }
535 
536         public void setId(String id) {
537             this.id = id;
538         }
539 
540         public String getFieldOne() {
541             return fieldOne;
542         }
543 
544         public void setFieldOne(String fieldOne) {
545             this.fieldOne = fieldOne;
546         }
547 
548         public Integer getFieldTwo() {
549             return fieldTwo;
550         }
551 
552         public void setFieldTwo(Integer fieldTwo) {
553             this.fieldTwo = fieldTwo;
554         }
555 
556         public DataObject2 getDataObject2() {
557             return dataObject2;
558         }
559 
560         public void setDataObject2(DataObject2 dataObject2) {
561             this.dataObject2 = dataObject2;
562         }
563 
564         public List<DataObject3> getDataObject3s() {
565             return dataObject3s;
566         }
567 
568         public void setDataObject3s(List<DataObject3> dataObject3s) {
569             this.dataObject3s = dataObject3s;
570         }
571 
572 		public String getDataObject2sKey() {
573 			return dataObject2sKey;
574 		}
575 
576 		public void setDataObject2sKey(String dataObject2sKey) {
577 			this.dataObject2sKey = dataObject2sKey;
578 		}
579 
580 		public DataObject2 getEagerDataObject2() {
581 			return eagerDataObject2;
582 		}
583 
584 		public void setEagerDataObject2(DataObject2 eagerDataObject2) {
585 			this.eagerDataObject2 = eagerDataObject2;
586 		}
587     }
588 
589     public static final class DataObject2 {
590         private String one;
591 		private String two; // FK for data object 4
592 		private DataObject4 dataObject4;
593 
594         public DataObject2() {}
595 
596         public DataObject2(String one, String two) {
597             this.one = one;
598             this.two = two;
599         }
600 
601         public String getOne() {
602             return one;
603         }
604 
605         public void setOne(String one) {
606             this.one = one;
607         }
608 
609         public String getTwo() {
610             return two;
611         }
612 
613         public void setTwo(String two) {
614             this.two = two;
615         }
616 
617 		public DataObject4 getDataObject4() {
618 			return dataObject4;
619         }
620 
621 		public void setDataObject4(DataObject4 dataObject3) {
622 			this.dataObject4 = dataObject3;
623         }
624     }
625 
626 	public static final class DataObject4 {
627 		private String pk;
628 		private String notPk;
629 
630 		public DataObject4(String pk, String notPk) {
631 			this.pk = pk;
632 			this.notPk = notPk;
633 		}
634 
635 		public String getPk() {
636 			return pk;
637 		}
638 
639 		public void setPk(String pk) {
640 			this.pk = pk;
641 		}
642 
643 		public String getNotPk() {
644 			return notPk;
645 		}
646 
647 		public void setNotPk(String notPk) {
648 			this.notPk = notPk;
649 		}
650 	}
651 
652     public static final class DataObject3 {
653 
654 		private String parentId;
655 		private String id;
656         private String hello;
657 		private String world; // FK for data object 2
658 		private DataObject2 dataObject2;
659 
660 		public DataObject3() {
661 		}
662 
663 		public DataObject3(String parentId, String id, String hello, String world) {
664 			super();
665 			this.parentId = parentId;
666 			this.id = id;
667 			this.hello = hello;
668 			this.world = world;
669 		}
670 
671 		public String getHello() {
672             return hello;
673         }
674 
675         public void setHello(String hello) {
676             this.hello = hello;
677         }
678 
679         public String getWorld() {
680             return world;
681         }
682 
683         public void setWorld(String world) {
684             this.world = world;
685         }
686 
687 		public String getParentId() {
688 			return parentId;
689 		}
690 
691 		public void setParentId(String parentId) {
692 			this.parentId = parentId;
693 		}
694 
695 		public String getId() {
696 			return id;
697 		}
698 
699 		public void setId(String id) {
700 			this.id = id;
701 		}
702 
703 		public DataObject2 getDataObject2() {
704 			return dataObject2;
705 		}
706 
707 		public void setDataObject2(DataObject2 dataObject2) {
708 			this.dataObject2 = dataObject2;
709 		}
710 
711     }
712 
713 }