Skip to content

Instantly share code, notes, and snippets.

@mp911de
Created March 26, 2021 13:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mp911de/3a99dc74d0f7d9ef9ddf5ddaebf57c35 to your computer and use it in GitHub Desktop.
Save mp911de/3a99dc74d0f7d9ef9ddf5ddaebf57c35 to your computer and use it in GitHub Desktop.
ProjectingConverter
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import java.beans.PropertyDescriptor;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.ProjectionInformation;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
/**
* @author Mark Paluch
*/
public class DefaultProjectingConverter extends MappingMongoConverter
implements ProjectingConverter<MongoPersistentEntity<?>, MongoPersistentProperty, Object, Document> {
public DefaultProjectingConverter(DbRefResolver dbRefResolver,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
super(dbRefResolver, mappingContext);
}
@Override
public <R> R project(ProjectionContext<MongoPersistentEntity<?>, MongoPersistentProperty, R> context,
Document source) {
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
ProjectionInformation projectionInformation = factory.getProjectionInformation(context.getTargetType());
MongoPersistentEntity<?> entity = context.getEntity();
MongoPersistentEntity<?> targetEntity = context.getEntity();
if (!projectionInformation.isClosed()) { // backed by real object
Object value = read(context.getEntity().getTypeInformation(), source);
return factory.createProjection(context.getTargetType(), value);
}
PersistentPropertyAccessor<?> accessor;
// TODO: EntityInstantiator
if (context.getTargetType().isInterface()) {
accessor = new MapPersistentPropertyAccessor();
} else {
targetEntity = mappingContext.getRequiredPersistentEntity(context.getTargetType());
try {
accessor = targetEntity.getPropertyAccessor(context.getTargetType().newInstance());
} catch (ReflectiveOperationException e) {
throw new RuntimeException("YarrRRRRrrrRRRr!");
}
}
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(source, spELContext);
DocumentAccessor documentAccessor = new DocumentAccessor(source);
PersistentPropertyAccessor<?> convertingAccessor = new ConvertingPropertyAccessor<>(accessor, conversionService);
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
ObjectPath.ROOT);
List<PropertyDescriptor> inputProperties = projectionInformation.getInputProperties();
for (PropertyDescriptor inputProperty : inputProperties) {
MongoPersistentProperty targetProperty = targetEntity.getRequiredPersistentProperty(inputProperty.getName());
MongoPersistentProperty persistentProperty = entity.getPersistentProperty(inputProperty.getName());
if (persistentProperty == null) {
continue;
}
if (persistentProperty.isCollectionLike()) {
Object value = documentAccessor.get(persistentProperty);
convertingAccessor.setProperty(targetProperty,
readCollectionOrArray(persistentProperty.getTypeInformation(),
ClassTypeInformation.fromReturnTypeOf(inputProperty.getReadMethod()), (Collection<?>) value,
ObjectPath.ROOT));
continue;
}
if (persistentProperty.isMap()) {
Object value = documentAccessor.get(persistentProperty);
convertingAccessor.setProperty(targetProperty, readMap(persistentProperty.getTypeInformation(),
ClassTypeInformation.fromReturnTypeOf(inputProperty.getReadMethod()), (Bson) value, ObjectPath.ROOT));
continue;
}
if (persistentProperty.isEntity()) {
if (!isCoercionOrSubtype(inputProperty, persistentProperty) && inputProperty.getPropertyType().isInterface()) { // potential
// sub-projection
Object value = documentAccessor.get(persistentProperty);
if (value instanceof Document) {
Document subdocument = (Document) value;
TypeInformation<?> typeInformation = typeMapper.readType(subdocument,
persistentProperty.getTypeInformation());
ProjectionContext<MongoPersistentEntity<?>, MongoPersistentProperty, ?> nested = createContext(
typeInformation.getType(), inputProperty.getPropertyType());
accessor.setProperty(persistentProperty, project(nested, subdocument));
} else {
throw new MappingException("Cannot map " + value + " to entity " + persistentProperty.getType());
}
continue;
}
}
Object propertyValue = valueProvider.getPropertyValue(persistentProperty);
convertingAccessor.setProperty(targetProperty, propertyValue);
}
if (context.getTargetType().isInterface()) {
return factory.createProjection(context.getTargetType(), accessor.getBean());
}
return (R) accessor.getBean();
}
@Override
<S> S doRead(TypeInformation<?> sourceType, TypeInformation<S> type, Bson bson, ObjectPath path) {
if (!sourceType.equals(type)) {
return project(createContext(sourceType.getType(), type.getType()), (Document) bson);
}
return super.doRead(sourceType, type, bson, path);
}
// PE: Contact, Projection: Person (Person extends Contact)
// PE: Person, Projection: Contact
// coercion or super-type
private boolean isCoercionOrSubtype(PropertyDescriptor inputProperty, MongoPersistentProperty persistentProperty) {
return inputProperty.getPropertyType().isAssignableFrom(persistentProperty.getType())
|| persistentProperty.getType().isAssignableFrom(inputProperty.getPropertyType());
}
@Override
public <R> ProjectionContext<MongoPersistentEntity<?>, MongoPersistentProperty, R> createContext(Class<?> entityType,
Class<R> targetType) {
return new ProjectionContext<MongoPersistentEntity<?>, MongoPersistentProperty, R>() {
@Override
public Class<R> getTargetType() {
return targetType;
}
@Override
public MongoPersistentEntity<?> getEntity() {
return mappingContext.getRequiredPersistentEntity(entityType);
}
};
}
static class MapPersistentPropertyAccessor implements PersistentPropertyAccessor<Map<String, Object>> {
Map<String, Object> map = new LinkedHashMap<>();
@Override
public void setProperty(PersistentProperty<?> persistentProperty, Object o) {
map.put(persistentProperty.getName(), o);
}
@Override
public Object getProperty(PersistentProperty<?> persistentProperty) {
return map.get(persistentProperty.getName());
}
@Override
public Map<String, Object> getBean() {
return map;
}
}
}
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
/**
* @author Mark Paluch
*/
public interface ProjectingConverter<E extends PersistentEntity<?, P>, P extends PersistentProperty<P>, T, S> {
/**
* Create a projection (DTO project, interface projection) using the given {@link ProjectionContext} from
* {@code source}.
*
* @param context
* @param source
* @param <R>
* @return
*/
<R extends T> R project(ProjectionContext<E, P, R> context, S source);
/**
* Create a {@link ProjectionContext} for a source entity and target projection type.
*
* @param entityType
* @param targetType
* @param <R>
* @return
*/
<R extends T> ProjectionContext<E, P, R> createContext(Class<?> entityType, Class<R> targetType);
/**
* Context encapsulating projection source and target type.
*
* @param <E>
* @param <P>
* @param <R>
*/
interface ProjectionContext<E extends PersistentEntity<?, P>, P extends PersistentProperty<P>, R> {
/**
* Projection target type.
*
* @return
*/
Class<R> getTargetType();
/**
* Source entity type.
*
* @return
*/
E getEntity();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment