diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index db824db71..34c08144a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.CollectionFactory; import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; @@ -54,6 +55,8 @@ import org.springframework.data.mapping.model.SpELExpressionParameterValueProvid import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent; +import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -75,6 +78,7 @@ import com.mongodb.DBRef; * @author Patrik Wasik * @author Thomas Darimont * @author Christoph Strobl + * @author Jordi Llach */ public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, ValueResolver { @@ -868,7 +872,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @Override public Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator, ObjectPath path) { - return new MongoDbPropertyValueProvider(dbo, evaluator, path).getPropertyValue(prop); + return new MongoDbPropertyValueProvider(dbo, evaluator, path, false).getPropertyValue(prop); } /** @@ -902,8 +906,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App Object dbObjItem = sourceValue.get(i); if (dbObjItem instanceof DBRef) { - items.add( - DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem), path)); + items.add(DBRef.class.equals(rawComponentType) ? dbObjItem + : readAndConvertDBRef((DBRef) dbObjItem, componentType, path, rawComponentType)); } else if (dbObjItem instanceof DBObject) { items.add(read(componentType, (DBObject) dbObjItem, path)); } else { @@ -955,7 +959,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App if (value instanceof DBObject) { map.put(key, read(valueType, (DBObject) value, path)); } else if (value instanceof DBRef) { - map.put(key, DBRef.class.equals(rawValueType) ? value : read(valueType, readRef((DBRef) value))); + map.put(key, DBRef.class.equals(rawValueType) ? value + : readAndConvertDBRef((DBRef) value, valueType, ObjectPath.ROOT, rawValueType)); } else { Class valueClass = valueType == null ? null : valueType.getType(); map.put(key, getPotentiallyConvertedSimpleRead(value, valueClass)); @@ -1111,6 +1116,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App private final DBObjectAccessor source; private final SpELExpressionEvaluator evaluator; private final ObjectPath path; + private final boolean ignoreLazyDBRefProperties; /** * Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and @@ -1121,13 +1127,18 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * @param path can be {@literal null}. */ public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path) { + this(source, evaluator, path, true); // ignoring by default + } + MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path, + boolean ignoreLazyDBRefProperties) { Assert.notNull(source); Assert.notNull(evaluator); this.source = new DBObjectAccessor(source); this.evaluator = evaluator; this.path = path; + this.ignoreLazyDBRefProperties = ignoreLazyDBRefProperties; } /* @@ -1142,6 +1153,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App if (value == null) { return null; } + if (this.ignoreLazyDBRefProperties && property.isDbReference() && property.getDBRef().lazy()) { // lazy DBRef, + // BasicDBList are + // resolved later + // by default + return null; + } return readValue(value, property.getTypeInformation(), path); } @@ -1203,14 +1220,28 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @SuppressWarnings("unchecked") private T potentiallyReadOrResolveDbRef(DBRef dbref, TypeInformation type, ObjectPath path, Class rawType) { - if (rawType.equals(DBRef.class)) { return (T) dbref; } - Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName()); + return (T) (object != null ? object : readAndConvertDBRef(dbref, type, path, rawType)); + } - return (T) (object != null ? object : read(type, readRef(dbref), path)); + private T readAndConvertDBRef(DBRef dbref, TypeInformation type, ObjectPath path, Class rawType) { + DBObject readRef = readRef(dbref); + final String collectionName = dbref.getCollectionName(); + if (canPublishEvent()) + ((ApplicationEventPublisher) this.applicationContext) + .publishEvent(new AfterLoadEvent(readRef, (Class) rawType, collectionName)); + T t = (T) read(type, readRef, path); + if (canPublishEvent()) + ((ApplicationEventPublisher) this.applicationContext) + .publishEvent(new AfterConvertEvent(readRef, t, collectionName)); + return t; + } + + private boolean canPublishEvent() { + return this.applicationContext != null; } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java index 2e03229a3..cefd922b0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java @@ -15,15 +15,20 @@ */ package org.springframework.data.mongodb.core.mapping.event; -import static org.hamcrest.core.Is.*; -import static org.junit.Assert.*; -import static org.springframework.data.mongodb.core.query.Criteria.*; -import static org.springframework.data.mongodb.core.query.Query.*; - +import com.mongodb.DB; +import com.mongodb.DBObject; +import com.mongodb.Mongo; +import com.mongodb.MongoClient; +import com.mongodb.WriteConcern; import java.net.UnknownHostException; - +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import static org.hamcrest.core.Is.is; import org.junit.After; import org.junit.Assert; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; @@ -31,24 +36,23 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.mapping.PersonPojoStringId; - -import com.mongodb.DB; -import com.mongodb.DBObject; -import com.mongodb.Mongo; -import com.mongodb.MongoClient; -import com.mongodb.WriteConcern; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; /** * Integration test for Mapping Events. * * @author Mark Pollack * @author Christoph Strobl + * @author Jordi Llach */ public class ApplicationContextEventTests { - private static final String COLLECTION_NAME = "personPojoStringId"; + private static final String COLLECTION_NAME = "personPojoStringId"; + private static final String ROOT_COLLECTION_NAME = "root"; + private static final String RELATED_COLLECTION_NAME = "related"; - private final String[] collectionsToDrop = new String[] { COLLECTION_NAME }; + private final String[] collectionsToDrop = new String[] { COLLECTION_NAME, ROOT_COLLECTION_NAME, RELATED_COLLECTION_NAME }; private ApplicationContext applicationContext; private MongoTemplate template; @@ -187,6 +191,149 @@ public class ApplicationContextEventTests { assertThat(simpleMappingEventListener.onAfterDeleteEvents.size(), is(1)); assertThat(simpleMappingEventListener.onAfterDeleteEvents.get(0).getCollectionName(), is(COLLECTION_NAME)); } + + /** + * DATAMONGO-1271 DATAMONGO-1287 + */ + @Test + public void loadAndConvertEventsInInnerSimpleDBRef () throws Exception { + ParentMappingEventListener simpleMappingEventListener = applicationContext.getBean(ParentMappingEventListener.class); + Related embed = new Related(1L, "embed desc"); + Related ref1 = new Related(2L, "related desc1"); + Related ref2 = new Related(3L, "related desc2"); + template.insert(embed); + template.insert(ref1); + template.insert(ref2); + + Root root = new Root(1L, embed, ref1, ref2, null, null, null, null); + template.insert(root); + + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0)); + + // initially fetching ROOT document and also eagerly fetching 1 DBRef + Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(2)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(2)); + + // checking that no event is fired because those documents were previously eagerly fetched + rootR.getRef().getDescription(); + rootR.getEmbed().getDescription(); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(2)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(2)); + // checking that accessing lazy DBRef fires 1 more event of each type + rootR.getLazyRef().getDescription(); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3)); + + // checking collectionNames fired + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(ROOT_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME)); + } + + /** + * DATAMONGO-1271 DATAMONGO-1287 + */ + @Test + public void loadAndConvertEventsInInnerListDBRef() throws Exception { + ParentMappingEventListener simpleMappingEventListener = applicationContext.getBean(ParentMappingEventListener.class); + Related embed = new Related(1L, "embed desc"); + Related ref1 = new Related(2L, "related desc1"); + Related ref2 = new Related(3L, "related desc2"); + template.insert(embed); + template.insert(ref1); + template.insert(ref2); + + Root root = new Root(1L, embed, null, null, Arrays.asList(ref1, ref2), Arrays.asList(ref1, ref2), null, null); + template.insert(root); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0)); + + // initially fetching ROOT document and also eagerly fetching 2 DBRef + Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3)); + + // checking that no event is fired because those documents were previously eagerly fetched + rootR.getListRef().get(0).getDescription(); + rootR.getListRef().get(1).getDescription(); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3)); + + // fetching lazily dbref + rootR.getListLazy().get(0).getDescription(); + rootR.getListLazy().get(1).getDescription(); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(5)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(5)); + + // checking collectionNames fired + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(ROOT_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME)); + } + + /** + * DATAMONGO-1271 DATAMONGO-1287 + */ + @Test + public void loadAndConvertEventsInInnerMapDBRef() throws Exception { + ParentMappingEventListener simpleMappingEventListener = applicationContext.getBean(ParentMappingEventListener.class); + Related embed = new Related(1L, "embed desc"); + Related ref1 = new Related(2L, "related desc1"); + Related ref2 = new Related(3L, "related desc2"); + template.insert(embed); + template.insert(ref1); + template.insert(ref2); + + Map mapRef = new HashMap(); + mapRef.put("1", ref1); + mapRef.put("2", ref2); + Map mapLazy = new HashMap(); + mapLazy.put("1", ref1); + mapLazy.put("2", ref2); + + Root root = new Root(1L, embed, null, null, null, null, mapRef, mapLazy); + template.insert(root); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0)); + + // initially fetching ROOT document and also eagerly fetching 2 DBRef (eager map) + Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3)); + + // checking that accessing eagerly fetched map does not fire any new event + Assert.assertEquals(0, rootR.getMapRef().keySet().stream().filter(key -> rootR.getMapRef().get(key).getDescription() == null).count()); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3)); + // accessing lazy map of dbref + Assert.assertEquals(0, rootR.getMapLazy().keySet().stream().filter(key -> rootR.getMapLazy().get(key).getDescription() == null).count()); + assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(5)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(5)); + + // checking collectionNames fired + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(ROOT_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterLoadEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME)); + assertThat(simpleMappingEventListener.onAfterConvertEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME)); + } private void comparePersonAndDbo(PersonPojoStringId p, PersonPojoStringId p2, DBObject dbo) { assertEquals(p.getId(), p2.getId()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTestsAppConfig.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTestsAppConfig.java index b990c23cd..4088ba5e2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTestsAppConfig.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTestsAppConfig.java @@ -51,4 +51,8 @@ public class ApplicationContextEventTestsAppConfig extends AbstractMongoConfigur return new SimpleMappingEventListener(); } + @Bean + public ParentMappingEventListener parentMappingEventListener() { + return new ParentMappingEventListener(); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Parent.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Parent.java new file mode 100644 index 000000000..d4d2fcb98 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Parent.java @@ -0,0 +1,14 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.springframework.data.mongodb.core.mapping.event; + +/** + * + * @author Jordi Llach + */ +public class Parent { + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ParentMappingEventListener.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ParentMappingEventListener.java new file mode 100644 index 000000000..15f2e4b81 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ParentMappingEventListener.java @@ -0,0 +1,28 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.springframework.data.mongodb.core.mapping.event; + +import java.util.ArrayList; + +/** + * + * @author Jordi Llach + */ +public class ParentMappingEventListener extends AbstractMongoEventListener { + + public final ArrayList> onAfterLoadEvents = new ArrayList>(); + public final ArrayList> onAfterConvertEvents = new ArrayList>(); + + @Override + public void onAfterLoad(AfterLoadEvent event) { + onAfterLoadEvents.add(event); + } + + @Override + public void onAfterConvert(AfterConvertEvent event) { + onAfterConvertEvents.add(event); + } +} \ No newline at end of file diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Related.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Related.java new file mode 100644 index 000000000..c4fcfb008 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Related.java @@ -0,0 +1,42 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.springframework.data.mongodb.core.mapping.event; + +import java.io.Serializable; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * + * @author Jordi Llach + */ +@Document +public class Related +extends Parent +implements Serializable { + + private static final long serialVersionUID = -5719343113953216434L; + + @Id + private Long id; + private String description; + + + @PersistenceConstructor + public Related(Long id, String description) { + this.id = id; + this.description = description; + } + + public Long getId() { + return id; + } + + public String getDescription() { + return description; + } +} \ No newline at end of file diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Root.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Root.java new file mode 100644 index 000000000..ad7811fd6 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/Root.java @@ -0,0 +1,95 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.springframework.data.mongodb.core.mapping.event; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * + * @author Jordi Llach + */ +@Document +public class Root +extends Parent +implements Serializable { + + private static final long serialVersionUID = -3211692873265644541L; + + @Id + private Long id; + + // simple + private Related embed; + + // dbref simple + @DBRef + private Related ref; + @DBRef(lazy = true) + private Related lazyRef; + + // collection support + @DBRef + private List listRef; + @DBRef(lazy = true) + private List listLazy; + + // map support + @DBRef + private Map mapRef; + @DBRef(lazy = true) + private Map mapLazy; + + @PersistenceConstructor + public Root(Long id, Related embed, Related ref, Related lazyRef, List listRef, List listLazy, + Map mapRef, Map mapLazy) { + this.id = id; + this.embed = embed; + this.ref = ref; + this.lazyRef = lazyRef; + this.listRef = listRef; + this.listLazy = listLazy; + this.mapRef = mapRef; + this.mapLazy = mapLazy; + } + + public Long getId() { + return id; + } + + public Related getEmbed() { + return embed; + } + + public Related getRef() { + return ref; + } + + public Related getLazyRef() { + return lazyRef; + } + + public List getListRef() { + return listRef; + } + + public List getListLazy() { + return listLazy; + } + + public Map getMapRef() { + return mapRef; + } + + public Map getMapLazy() { + return mapLazy; + } +} \ No newline at end of file