Polishing.

Encapsulate nested object lookup. Refine method signatures and tweak Javadoc.

See #4098
Original pull request: #4133.
This commit is contained in:
Mark Paluch
2022-08-05 15:58:27 +02:00
parent 1e7dc7ce66
commit 5fc49b1649

View File

@@ -384,81 +384,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return readDocument(context, source, typeHint);
}
static class AssociationConversionContext implements ConversionContext {
private final ConversionContext delegate;
public AssociationConversionContext(ConversionContext delegate) {
this.delegate = delegate;
}
@Override
public <S> S convert(Object source, TypeInformation<? extends S> typeHint, ConversionContext context) {
return delegate.convert(source, typeHint, context);
}
@Override
public ConversionContext withPath(ObjectPath currentPath) {
return new AssociationConversionContext(delegate.withPath(currentPath));
}
@Override
public ObjectPath getPath() {
return delegate.getPath();
}
@Override
public CustomConversions getCustomConversions() {
return delegate.getCustomConversions();
}
@Override
public MongoConverter getSourceConverter() {
return delegate.getSourceConverter();
}
@Override
public boolean resolveIdsInContext() {
return true;
}
}
class ProjectingConversionContext extends DefaultConversionContext {
private final EntityProjection<?, ?> returnedTypeDescriptor;
ProjectingConversionContext(MongoConverter sourceConverter, CustomConversions customConversions, ObjectPath path,
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Bson> mapConverter,
ContainerValueConverter<DBRef> dbRefConverter, ValueConverter<Object> elementConverter,
EntityProjection<?, ?> projection) {
super(sourceConverter, customConversions, path,
(context, source, typeHint) -> doReadOrProject(context, source, typeHint, projection),
collectionConverter, mapConverter, dbRefConverter, elementConverter);
this.returnedTypeDescriptor = projection;
}
@Override
public DefaultConversionContext forProperty(String name) {
EntityProjection<?, ?> property = returnedTypeDescriptor.findProperty(name);
if (property == null) {
return new DefaultConversionContext(sourceConverter, conversions, path,
MappingMongoConverter.this::readDocument, collectionConverter, mapConverter, dbRefConverter,
elementConverter);
}
return new ProjectingConversionContext(sourceConverter, conversions, path, collectionConverter, mapConverter,
dbRefConverter, elementConverter, property);
}
@Override
public DefaultConversionContext withPath(ObjectPath currentPath) {
return new ProjectingConversionContext(sourceConverter, conversions, currentPath, collectionConverter,
mapConverter, dbRefConverter, elementConverter, returnedTypeDescriptor);
}
}
static class MapPersistentPropertyAccessor implements PersistentPropertyAccessor<Map<String, Object>> {
Map<String, Object> map = new LinkedHashMap<>();
@@ -565,16 +490,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private <S> S read(ConversionContext context, MongoPersistentEntity<S> entity, Document bson) {
S existing = context.findContextualEntity(entity, bson);
if (existing != null) {
return existing;
}
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
if (context.resolveIdsInContext() && hasIdentifier(bson)) {
S existing = findContextualEntity(context, entity, bson);
if (existing != null) {
return existing;
}
}
PreferredConstructor<S, MongoPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
ParameterValueProvider<MongoPersistentProperty> provider = persistenceConstructor != null
@@ -592,16 +515,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return instance;
}
private boolean hasIdentifier(Document bson) {
return bson.get(BasicMongoPersistentProperty.ID_FIELD_NAME) != null;
}
@Nullable
private <S> S findContextualEntity(ConversionContext context, MongoPersistentEntity<S> entity, Document bson) {
return context.getPath().getPathItem(bson.get(BasicMongoPersistentProperty.ID_FIELD_NAME), entity.getCollection(),
entity.getType());
}
private <S> S populateProperties(ConversionContext context, MongoPersistentEntity<S> entity,
DocumentAccessor documentAccessor, SpELExpressionEvaluator evaluator, S instance) {
@@ -2197,40 +2110,130 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
}
/**
* Conversion context defining an interface for graph-traversal-based conversion of documents. Entrypoint for
* recursive conversion of {@link Document} and other types.
*
* @since 3.4.3
*/
interface ConversionContext {
/**
* Converts a source object into {@link TypeInformation target}.
*
* @param source must not be {@literal null}.
* @param typeHint must not be {@literal null}.
* @return the converted object.
*/
default <S extends Object> S convert(Object source, TypeInformation<? extends S> typeHint) {
return convert(source, typeHint, this);
}
/**
* Converts a source object into {@link TypeInformation target}.
*
* @param source must not be {@literal null}.
* @param typeHint must not be {@literal null}.
* @param context must not be {@literal null}.
* @return the converted object.
*/
<S extends Object> S convert(Object source, TypeInformation<? extends S> typeHint, ConversionContext context);
/**
* Create a new {@link ConversionContext} with {@link ObjectPath currentPath} applied.
*
* @param currentPath must not be {@literal null}.
* @return a new {@link ConversionContext} with {@link ObjectPath currentPath} applied.
*/
ConversionContext withPath(ObjectPath currentPath);
ObjectPath getPath();
/**
* Obtain a {@link ConversionContext} for the given property {@code name}.
*
* @param name must not be {@literal null}.
* @return the {@link ConversionContext} to be used for conversion of the given property.
*/
default ConversionContext forProperty(String name) {
return this;
}
default ConversionContext forProperty(@Nullable PersistentProperty property) {
/**
* Obtain a {@link ConversionContext} for the given {@link MongoPersistentProperty}.
*
* @param property must not be {@literal null}.
* @return the {@link ConversionContext} to be used for conversion of the given property.
*/
default ConversionContext forProperty(MongoPersistentProperty property) {
if (property != null) {
if (property.isAssociation()) {
return new AssociationConversionContext(forProperty(property.getName()));
}
return forProperty(property.getName());
}
return this;
return property.isAssociation() ? new AssociationConversionContext(forProperty(property.getName()))
: forProperty(property.getName());
}
default boolean resolveIdsInContext() {
return false;
/**
* Lookup a potentially existing entity instance of the given {@link MongoPersistentEntity} and {@link Document}
*
* @param entity
* @param document
* @return
* @param <S>
*/
@Nullable
default <S> S findContextualEntity(MongoPersistentEntity<S> entity, Document document) {
return null;
}
ObjectPath getPath();
CustomConversions getCustomConversions();
MongoConverter getSourceConverter();
}
/**
* @since 3.4.3
*/
static class AssociationConversionContext implements ConversionContext {
private final ConversionContext delegate;
public AssociationConversionContext(ConversionContext delegate) {
this.delegate = delegate;
}
@Override
public <S> S convert(Object source, TypeInformation<? extends S> typeHint, ConversionContext context) {
return delegate.convert(source, typeHint, context);
}
@Override
public ConversionContext withPath(ObjectPath currentPath) {
return new AssociationConversionContext(delegate.withPath(currentPath));
}
@Override
public <S> S findContextualEntity(MongoPersistentEntity<S> entity, Document document) {
Object identifier = document.get(BasicMongoPersistentProperty.ID_FIELD_NAME);
return identifier != null ? getPath().getPathItem(identifier, entity.getCollection(), entity.getType()) : null;
}
@Override
public ObjectPath getPath() {
return delegate.getPath();
}
@Override
public CustomConversions getCustomConversions() {
return delegate.getCustomConversions();
}
@Override
public MongoConverter getSourceConverter() {
return delegate.getSourceConverter();
}
}
/**
@@ -2266,14 +2269,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this.elementConverter = elementConverter;
}
/**
* Converts a source object into {@link TypeInformation target}.
*
* @param source must not be {@literal null}.
* @param typeHint must not be {@literal null}.
* @return the converted object.
*/
@SuppressWarnings("unchecked")
@Override
public <S extends Object> S convert(Object source, TypeInformation<? extends S> typeHint,
ConversionContext context) {
@@ -2339,13 +2336,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return sourceConverter;
}
/**
* Create a new {@link DefaultConversionContext} with {@link ObjectPath currentPath} applied.
*
* @param currentPath must not be {@literal null}.
* @return a new {@link DefaultConversionContext} with {@link ObjectPath currentPath} applied.
*/
public DefaultConversionContext withPath(ObjectPath currentPath) {
@Override
public ConversionContext withPath(ObjectPath currentPath) {
Assert.notNull(currentPath, "ObjectPath must not be null");
@@ -2353,14 +2345,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
collectionConverter, mapConverter, dbRefConverter, elementConverter);
}
@Override
public ObjectPath getPath() {
return path;
}
public DefaultConversionContext forProperty(String name) {
return this;
}
/**
* Converts a simple {@code source} value into {@link TypeInformation the target type}.
*
@@ -2386,6 +2375,45 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
/**
* @since 3.4.3
*/
class ProjectingConversionContext extends DefaultConversionContext {
private final EntityProjection<?, ?> returnedTypeDescriptor;
ProjectingConversionContext(MongoConverter sourceConverter, CustomConversions customConversions, ObjectPath path,
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Bson> mapConverter,
ContainerValueConverter<DBRef> dbRefConverter, ValueConverter<Object> elementConverter,
EntityProjection<?, ?> projection) {
super(sourceConverter, customConversions, path,
(context, source, typeHint) -> doReadOrProject(context, source, typeHint, projection),
collectionConverter, mapConverter, dbRefConverter, elementConverter);
this.returnedTypeDescriptor = projection;
}
@Override
public ConversionContext forProperty(String name) {
EntityProjection<?, ?> property = returnedTypeDescriptor.findProperty(name);
if (property == null) {
return new DefaultConversionContext(sourceConverter, conversions, path,
MappingMongoConverter.this::readDocument, collectionConverter, mapConverter, dbRefConverter,
elementConverter);
}
return new ProjectingConversionContext(sourceConverter, conversions, path, collectionConverter, mapConverter,
dbRefConverter, elementConverter, property);
}
@Override
public ConversionContext withPath(ObjectPath currentPath) {
return new ProjectingConversionContext(sourceConverter, conversions, currentPath, collectionConverter,
mapConverter, dbRefConverter, elementConverter, returnedTypeDescriptor);
}
}
private static class PropertyTranslatingPropertyAccessor<T> implements PersistentPropertyPathAccessor<T> {
private final PersistentPropertyAccessor<T> delegate;