DATAMONGO-1992 - Extract common entity operations API from (Reactive)MongoTemplate.
Introduced EntityOperations and MappedDocument to allow to share common operations from MongoTemplate and ReactiveMongoTemplate.
This commit is contained in:
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.mongodb.util.JSONParseException;
|
||||
|
||||
/**
|
||||
* Common operations performed on an entity in the context of it's mapping metadata.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
* @see MongoTemplate
|
||||
* @see ReactiveMongoTemplate
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class EntityOperations {
|
||||
|
||||
private static final String ID_FIELD = "_id";
|
||||
|
||||
private final @NonNull MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Entity} for the given bean.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> Entity<T> forEntity(T entity) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
|
||||
if (entity instanceof String) {
|
||||
return new SimpleEntity(parse(entity.toString()));
|
||||
}
|
||||
|
||||
if (entity instanceof Map) {
|
||||
return new SimpleEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return MappedEntity.of(entity, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
if (entity instanceof String) {
|
||||
return new SimpleEntity(parse(entity.toString()));
|
||||
}
|
||||
|
||||
if (entity instanceof Map) {
|
||||
return new SimpleEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return AdaptibleMappedEntity.of(entity, context, conversionService);
|
||||
}
|
||||
|
||||
public String determineCollectionName(@Nullable Class<?> entityClass) {
|
||||
|
||||
if (entityClass == null) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"No class parameter provided, entity collection can't be determined!");
|
||||
}
|
||||
|
||||
return context.getRequiredPersistentEntity(entityClass).getCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collection name to be used for the given entity.
|
||||
*
|
||||
* @param obj can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String determineEntityCollectionName(@Nullable Object obj) {
|
||||
return null == obj ? null : determineCollectionName(obj.getClass());
|
||||
}
|
||||
|
||||
public Query getByIdInQuery(Collection<?> entities) {
|
||||
|
||||
MultiValueMap<String, Object> byIds = new LinkedMultiValueMap<>();
|
||||
|
||||
entities.stream() //
|
||||
.map(this::forEntity) //
|
||||
.forEach(it -> byIds.add(it.getIdFieldName(), it.getId()));
|
||||
|
||||
Criteria[] criterias = byIds.entrySet().stream() //
|
||||
.map(it -> Criteria.where(it.getKey()).in(it.getValue())) //
|
||||
.toArray(Criteria[]::new);
|
||||
|
||||
return new Query(criterias.length == 1 ? criterias[0] : new Criteria().orOperator(criterias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the identifier property. Considers mapping information but falls back to the MongoDB default of
|
||||
* {@code _id} if no identifier property can be found.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String getIdPropertyName(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = context.getPersistentEntity(type);
|
||||
|
||||
if (persistentEntity != null && persistentEntity.getIdProperty() != null) {
|
||||
return persistentEntity.getRequiredIdProperty().getName();
|
||||
}
|
||||
|
||||
return ID_FIELD;
|
||||
}
|
||||
|
||||
private static Document parse(String source) {
|
||||
|
||||
try {
|
||||
return Document.parse(source);
|
||||
} catch (JSONParseException | org.bson.json.JsonParseException o_O) {
|
||||
throw new MappingException("Could not parse given String to save into a JSON document!", o_O);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of information about an entity.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
interface Entity<T> {
|
||||
|
||||
/**
|
||||
* Returns the field name of the identifier of the entity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getIdFieldName();
|
||||
|
||||
/**
|
||||
* Returns the identifier of the entity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object getId();
|
||||
|
||||
/**
|
||||
* Returns the {@link Query} to find the entity by its identifier.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Query getByIdQuery();
|
||||
|
||||
/**
|
||||
* Returns the {@link Query} to find the entity in its current version.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Query getQueryForVersion();
|
||||
|
||||
/**
|
||||
* Maps the backing entity into a {@link MappedDocument} using the given {@link MongoWriter}.
|
||||
*
|
||||
* @param writer must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
MappedDocument toMappedDocument(MongoWriter<? super T> writer);
|
||||
|
||||
/**
|
||||
* Asserts that the identifier type is updatable in case its not already set.
|
||||
*/
|
||||
default void assertUpdateableIdIfNotSet() {}
|
||||
|
||||
/**
|
||||
* Returns whether the entity is versioned, i.e. if it contains a version property.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default boolean isVersionedEntity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the version if the entity has a version property, {@literal null} otherwise.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object getVersion();
|
||||
|
||||
/**
|
||||
* Returns the underlying bean.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
T getBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Information and commands on an entity.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
interface AdaptibleEntity<T> extends Entity<T> {
|
||||
|
||||
/**
|
||||
* Populates the identifier of the backing entity if it has an identifier property and there's no identifier
|
||||
* currently present.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
T populateIdIfNecessary(@Nullable Object id);
|
||||
|
||||
/**
|
||||
* Initializes the version property of the of the current entity if available.
|
||||
*
|
||||
* @return the entity with the version property updated if available.
|
||||
*/
|
||||
T initializeVersionProperty();
|
||||
|
||||
/**
|
||||
* Increments the value of the version property if available.
|
||||
*
|
||||
* @return the entity with the version property incremented if available.
|
||||
*/
|
||||
T incrementVersion();
|
||||
|
||||
/**
|
||||
* Returns the current version value if the entity has a version property.
|
||||
*
|
||||
* @return the current version or {@literal null} in case it's uninitialized or the entity doesn't expose a version
|
||||
* property.
|
||||
*/
|
||||
@Nullable
|
||||
Number getVersion();
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class SimpleEntity<T extends Map<String, Object>> implements AdaptibleEntity<T> {
|
||||
|
||||
private final T map;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getIdPropertyName()
|
||||
*/
|
||||
@Override
|
||||
public String getIdFieldName() {
|
||||
return ID_FIELD;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return map.get(ID_FIELD);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getByIdQuery()
|
||||
*/
|
||||
@Override
|
||||
public Query getByIdQuery() {
|
||||
return Query.query(Criteria.where(ID_FIELD).is(map.get(ID_FIELD)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#populateIdIfNecessary(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T populateIdIfNecessary(@Nullable Object id) {
|
||||
|
||||
map.put(ID_FIELD, id);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getQueryForVersion()
|
||||
*/
|
||||
@Override
|
||||
public Query getQueryForVersion() {
|
||||
throw new MappingException("Cannot query for version on plain Documents!");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#toMappedDocument(org.springframework.data.mongodb.core.convert.MongoWriter)
|
||||
*/
|
||||
@Override
|
||||
public MappedDocument toMappedDocument(MongoWriter<? super T> writer) {
|
||||
return MappedDocument.of(map instanceof Document //
|
||||
? (Document) map //
|
||||
: new Document(map));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Number getVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
private static class MappedEntity<T> implements Entity<T> {
|
||||
|
||||
private final @NonNull MongoPersistentEntity<?> entity;
|
||||
private final @NonNull IdentifierAccessor idAccessor;
|
||||
private final @NonNull PersistentPropertyAccessor<T> propertyAccessor;
|
||||
|
||||
private static <T> MappedEntity<T> of(T bean,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
|
||||
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new MappedEntity<>(entity, identifierAccessor, propertyAccessor);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getIdPropertyName()
|
||||
*/
|
||||
@Override
|
||||
public String getIdFieldName() {
|
||||
return entity.getRequiredIdProperty().getFieldName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return idAccessor.getRequiredIdentifier();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getByIdQuery()
|
||||
*/
|
||||
@Override
|
||||
public Query getByIdQuery() {
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
throw new MappingException("No id property found for object of type " + entity.getType() + "!");
|
||||
}
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
|
||||
return Query.query(Criteria.where(idProperty.getName()).is(getId()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getQueryForVersion(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Query getQueryForVersion() {
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
MongoPersistentProperty property = entity.getRequiredVersionProperty();
|
||||
|
||||
return new Query(Criteria.where(idProperty.getName()).is(getId())//
|
||||
.and(property.getName()).is(getVersion()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#toMappedDocument(org.springframework.data.mongodb.core.convert.MongoWriter)
|
||||
*/
|
||||
@Override
|
||||
public MappedDocument toMappedDocument(MongoWriter<? super T> writer) {
|
||||
|
||||
T bean = propertyAccessor.getBean();
|
||||
|
||||
Document document = new Document();
|
||||
writer.write(bean, document);
|
||||
|
||||
if (document.containsKey(ID_FIELD) && document.get(ID_FIELD) == null) {
|
||||
document.remove(ID_FIELD);
|
||||
}
|
||||
|
||||
return MappedDocument.of(document);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.Entity#assertUpdateableIdIfNotSet()
|
||||
*/
|
||||
public void assertUpdateableIdIfNotSet() {
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MongoPersistentProperty property = entity.getRequiredIdProperty();
|
||||
Object propertyValue = idAccessor.getIdentifier();
|
||||
|
||||
if (propertyValue != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(property.getType())) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Cannot autogenerate id of type %s for entity of type %s!", property.getType().getName(),
|
||||
entity.getType().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#isVersionedEntity()
|
||||
*/
|
||||
@Override
|
||||
public boolean isVersionedEntity() {
|
||||
return entity.hasVersionProperty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getVersion() {
|
||||
return propertyAccessor.getProperty(entity.getRequiredVersionProperty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
}
|
||||
|
||||
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
|
||||
|
||||
private final MongoPersistentEntity<?> entity;
|
||||
private final ConvertingPropertyAccessor<T> propertyAccessor;
|
||||
private final IdentifierAccessor identifierAccessor;
|
||||
|
||||
private AdaptibleMappedEntity(MongoPersistentEntity<?> entity, IdentifierAccessor identifierAccessor,
|
||||
ConvertingPropertyAccessor<T> propertyAccessor) {
|
||||
|
||||
super(entity, identifierAccessor, propertyAccessor);
|
||||
|
||||
this.entity = entity;
|
||||
this.propertyAccessor = propertyAccessor;
|
||||
this.identifierAccessor = identifierAccessor;
|
||||
}
|
||||
|
||||
private static <T> AdaptibleEntity<T> of(T bean,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context,
|
||||
ConversionService conversionService) {
|
||||
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new AdaptibleMappedEntity<>(entity, identifierAccessor,
|
||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T populateIdIfNecessary(@Nullable Object id) {
|
||||
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T bean = propertyAccessor.getBean();
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
if (idProperty == null) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
if (identifierAccessor.getIdentifier() != null) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
propertyAccessor.setProperty(idProperty, id);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MappedEntity#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Number getVersion() {
|
||||
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
|
||||
return propertyAccessor.getProperty(versionProperty, Number.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
|
||||
if (!entity.hasVersionProperty()) {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
propertyAccessor.setProperty(entity.getRequiredVersionProperty(), 0);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
Number version = getVersion();
|
||||
Number nextVersion = version == null ? 0 : version.longValue() + 1;
|
||||
|
||||
propertyAccessor.setProperty(versionProperty, nextVersion);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,11 +119,11 @@ class ExecutableAggregationOperationSupport implements ExecutableAggregationOper
|
||||
TypedAggregation<?> typedAggregation = (TypedAggregation<?>) aggregation;
|
||||
|
||||
if (typedAggregation.getInputType() != null) {
|
||||
return template.determineCollectionName(typedAggregation.getInputType());
|
||||
return template.getCollectionName(typedAggregation.getInputType());
|
||||
}
|
||||
}
|
||||
|
||||
return template.determineCollectionName(domainType);
|
||||
return template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
|
||||
private String asString() {
|
||||
|
||||
@@ -129,7 +129,7 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -67,8 +68,9 @@ class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperatio
|
||||
private final @Nullable String reduceFunction;
|
||||
private final @Nullable MapReduceOptions options;
|
||||
|
||||
ExecutableMapReduceSupport(MongoTemplate template, Class<?> domainType, Class<T> returnType, @Nullable String collection,
|
||||
Query query, @Nullable String mapFunction, @Nullable String reduceFunction, @Nullable MapReduceOptions options) {
|
||||
ExecutableMapReduceSupport(MongoTemplate template, Class<?> domainType, Class<T> returnType,
|
||||
@Nullable String collection, Query query, @Nullable String mapFunction, @Nullable String reduceFunction,
|
||||
@Nullable MapReduceOptions options) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
@@ -169,7 +171,7 @@ class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperatio
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
|
||||
import com.mongodb.client.model.Filters;
|
||||
|
||||
/**
|
||||
* A MongoDB document in its mapped state. I.e. after a source document has been mapped using mapping information of the
|
||||
* entity the source document was supposed to represent.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public class MappedDocument {
|
||||
|
||||
private static final String ID_FIELD = "_id";
|
||||
private static final Document ID_ONLY_PROJECTION = new Document(ID_FIELD, 1);
|
||||
|
||||
private final @Getter Document document;
|
||||
|
||||
public static Document getIdOnlyProjection() {
|
||||
return ID_ONLY_PROJECTION;
|
||||
}
|
||||
|
||||
public static Document getIdIn(Collection<?> ids) {
|
||||
return new Document(ID_FIELD, new Document("$in", ids));
|
||||
}
|
||||
|
||||
public static List<Object> toIds(Collection<Document> documents) {
|
||||
|
||||
return documents.stream()//
|
||||
.map(it -> it.get(ID_FIELD))//
|
||||
.collect(StreamUtils.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public boolean hasId() {
|
||||
return document.containsKey(ID_FIELD);
|
||||
}
|
||||
|
||||
public boolean hasNonNullId() {
|
||||
return hasId() && document.get(ID_FIELD) != null;
|
||||
}
|
||||
|
||||
public Object getId() {
|
||||
return document.get(ID_FIELD);
|
||||
}
|
||||
|
||||
public <T> T getId(Class<T> type) {
|
||||
return document.get(ID_FIELD, type);
|
||||
}
|
||||
|
||||
public boolean isIdPresent(Class<?> type) {
|
||||
return type.isInstance(getId());
|
||||
}
|
||||
|
||||
public Bson getIdFilter() {
|
||||
return Filters.eq(ID_FIELD, document.get(ID_FIELD));
|
||||
}
|
||||
|
||||
public Update updateWithoutId() {
|
||||
return Update.fromDocument(document, ID_FIELD);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,5 +30,4 @@ import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
public interface ReactiveCollectionCallback<T> {
|
||||
|
||||
Publisher<T> doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException;
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
@@ -24,9 +23,20 @@ import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -49,32 +59,39 @@ import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResult;
|
||||
import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.MappingContextEvent;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefProxyHandler;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolverCallback;
|
||||
import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
@@ -112,7 +129,6 @@ import org.springframework.data.mongodb.util.MongoClientVersion;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -129,6 +145,15 @@ import com.mongodb.Mongo;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import com.mongodb.client.model.ReturnDocument;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.model.ValidationOptions;
|
||||
import com.mongodb.client.model.*;
|
||||
import com.mongodb.client.model.changestream.FullDocument;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
@@ -143,8 +168,6 @@ import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
import com.mongodb.reactivestreams.client.Success;
|
||||
import com.mongodb.util.JSONParseException;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
/**
|
||||
* Primary implementation of {@link ReactiveMongoOperations}. It simplifies the use of Reactive MongoDB usage and helps
|
||||
@@ -165,7 +188,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
public static final DbRefResolver NO_OP_REF_RESOLVER = NoOpDbRefResolver.INSTANCE;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveMongoTemplate.class);
|
||||
private static final String ID_FIELD = "_id";
|
||||
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
|
||||
private static final Collection<Class<?>> ITERABLE_CLASSES;
|
||||
|
||||
@@ -189,6 +211,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final JsonSchemaMapper schemaMapper;
|
||||
private final SpelAwareProxyProjectionFactory projectionFactory;
|
||||
private final ApplicationListener<MappingContextEvent<?, ?>> indexCreatorListener;
|
||||
private final EntityOperations operations;
|
||||
|
||||
private @Nullable WriteConcern writeConcern;
|
||||
private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
|
||||
@@ -253,6 +276,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
// We always have a mapping context in the converter, whether it's a simple one or not
|
||||
this.mappingContext = this.mongoConverter.getMappingContext();
|
||||
this.operations = new EntityOperations(this.mappingContext);
|
||||
|
||||
// We create indexes based on mapping events
|
||||
if (this.mappingContext instanceof MongoMappingContext) {
|
||||
@@ -279,6 +303,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.indexCreator = that.indexCreator;
|
||||
this.indexCreatorListener = that.indexCreatorListener;
|
||||
this.mappingContext = that.mappingContext;
|
||||
this.operations = that.operations;
|
||||
}
|
||||
|
||||
private void onCheckForIndexes(MongoPersistentEntity<?> entity, Consumer<Throwable> subscriptionExceptionHandler) {
|
||||
@@ -820,10 +845,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
public <T> Mono<T> findById(Object id, Class<T> entityClass, String collectionName) {
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityClass);
|
||||
MongoPersistentProperty idProperty = persistentEntity != null ? persistentEntity.getIdProperty() : null;
|
||||
|
||||
String idKey = idProperty == null ? ID_FIELD : idProperty.getName();
|
||||
String idKey = operations.getIdPropertyName(entityClass);
|
||||
|
||||
return doFindOne(collectionName, new Document(idKey, id), null, entityClass, null);
|
||||
}
|
||||
@@ -855,8 +877,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
|
||||
String mappedFieldName = queryMapper.getMappedFields(new Document(field, 1), entity).keySet().iterator().next();
|
||||
|
||||
Class<T> mongoDriverCompatibleType = mongoDatabaseFactory.getCodecFor(resultClass).map(Codec::getEncoderClass)
|
||||
.orElse((Class) BsonValue.class);
|
||||
Class<T> mongoDriverCompatibleType = mongoDatabaseFactory.getCodecFor(resultClass) //
|
||||
.map(Codec::getEncoderClass) //
|
||||
.orElse((Class<T>) BsonValue.class);
|
||||
|
||||
Flux<?> result = execute(collectionName, collection -> {
|
||||
|
||||
@@ -1006,11 +1029,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#geoNear(org.springframework.data.mongodb.core.query.NearQuery, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<T> entityClass, String collectionName) {
|
||||
return geoNear(near, entityClass, collectionName, entityClass);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<?> entityClass, String collectionName,
|
||||
Class<T> returnType) {
|
||||
|
||||
@@ -1045,11 +1068,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return executeCommand(command, this.readPreference).flatMapMany(document -> {
|
||||
|
||||
List<Document> l = document.get("results", List.class);
|
||||
if (l == null) {
|
||||
return Flux.empty();
|
||||
}
|
||||
return Flux.fromIterable(l);
|
||||
List<Document> results = document.get("results", List.class);
|
||||
|
||||
return results == null ? Flux.empty() : Flux.fromIterable(results);
|
||||
|
||||
}).skip(near.getSkip() != null ? near.getSkip() : 0).map(callback::doWith);
|
||||
});
|
||||
}
|
||||
@@ -1122,7 +1144,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Document mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), entity);
|
||||
Document mappedSort = queryMapper.getMappedSort(query.getSortObject(), entity);
|
||||
|
||||
Document mappedReplacement = toDocument(replacement, this.mongoConverter);
|
||||
Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument();
|
||||
|
||||
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
|
||||
query.getCollation().map(Collation::toMongoCollation).orElse(null), entityType, mappedReplacement, options,
|
||||
@@ -1253,16 +1275,18 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
T toSave = (T) initializeVersionProperty(objectToSave);
|
||||
AdaptibleEntity<T> entity = operations.forEntity(objectToSave, mongoConverter.getConversionService());
|
||||
T toSave = entity.initializeVersionProperty();
|
||||
|
||||
maybeEmitEvent(new BeforeConvertEvent<>(toSave, collectionName));
|
||||
|
||||
Document dbDoc = toDocument(toSave, writer);
|
||||
Document dbDoc = entity.toMappedDocument(writer).getDocument();
|
||||
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(toSave, dbDoc, collectionName));
|
||||
|
||||
Mono<T> afterInsert = insertDBObject(collectionName, dbDoc, toSave.getClass()).map(id -> {
|
||||
|
||||
T saved = (T) populateIdIfNecessary(toSave, id);
|
||||
T saved = entity.populateIdIfNecessary(id);
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName));
|
||||
return saved;
|
||||
});
|
||||
@@ -1327,19 +1351,21 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(writer, "MongoWriter must not be null!");
|
||||
|
||||
Mono<List<Tuple2<T, Document>>> prepareDocuments = Flux.fromIterable(batchToSave)
|
||||
.map(o -> {
|
||||
Mono<List<Tuple2<AdaptibleEntity<T>, Document>>> prepareDocuments = Flux.fromIterable(batchToSave).map(o -> {
|
||||
|
||||
T toSave = (T) initializeVersionProperty(o);
|
||||
maybeEmitEvent(new BeforeConvertEvent<>(toSave, collectionName));
|
||||
AdaptibleEntity<T> entity = operations.forEntity(o, mongoConverter.getConversionService());
|
||||
T toSave = entity.initializeVersionProperty();
|
||||
|
||||
Document dbDoc = toDocument(toSave, writer);
|
||||
BeforeConvertEvent<T> event = new BeforeConvertEvent<>(toSave, collectionName);
|
||||
toSave = maybeEmitEvent(event).getSource();
|
||||
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(toSave, dbDoc, collectionName));
|
||||
return Tuples.of(toSave, dbDoc);
|
||||
}).collectList();
|
||||
Document dbDoc = entity.toMappedDocument(writer).getDocument();
|
||||
|
||||
Flux<Tuple2<T, Document>> insertDocuments = prepareDocuments.flatMapMany(tuples -> {
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(toSave, dbDoc, collectionName));
|
||||
return Tuples.of(entity, dbDoc);
|
||||
}).collectList();
|
||||
|
||||
Flux<Tuple2<AdaptibleEntity<T>, Document>> insertDocuments = prepareDocuments.flatMapMany(tuples -> {
|
||||
|
||||
List<Document> dbObjects = tuples.stream().map(Tuple2::getT2).collect(Collectors.toList());
|
||||
|
||||
@@ -1348,7 +1374,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return insertDocuments.map(tuple -> {
|
||||
|
||||
T saved = (T) populateIdIfNecessary(tuple.getT1(), tuple.getT2().get(ID_FIELD));
|
||||
Object id = MappedDocument.of(tuple.getT2()).getId();
|
||||
|
||||
T saved = tuple.getT1().populateIdIfNecessary(id);
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, tuple.getT2(), collectionName));
|
||||
return saved;
|
||||
});
|
||||
@@ -1409,48 +1437,33 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
private <T> Mono<T> doSaveVersioned(T objectToSave, MongoPersistentEntity<?> entity, String collectionName) {
|
||||
|
||||
AdaptibleEntity<T> forEntity = operations.forEntity(objectToSave, mongoConverter.getConversionService());
|
||||
|
||||
return createMono(collectionName, collection -> {
|
||||
|
||||
ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor(
|
||||
entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService());
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
|
||||
Object version = convertingAccessor.getProperty(versionProperty);
|
||||
Number versionNumber = convertingAccessor.getProperty(versionProperty, Number.class);
|
||||
Number versionNumber = forEntity.getVersion();
|
||||
|
||||
// Fresh instance -> initialize version property
|
||||
if (version == null) {
|
||||
if (versionNumber == null) {
|
||||
return doInsert(collectionName, objectToSave, mongoConverter);
|
||||
}
|
||||
|
||||
assertUpdateableIdIfNotSet(objectToSave);
|
||||
forEntity.assertUpdateableIdIfNotSet();
|
||||
|
||||
// Create query for entity with the id and old version
|
||||
Object id = convertingAccessor.getProperty(idProperty);
|
||||
Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version));
|
||||
Query query = forEntity.getQueryForVersion();
|
||||
|
||||
if (versionNumber == null) {
|
||||
versionNumber = 0;
|
||||
}
|
||||
// Bump version number
|
||||
convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1);
|
||||
T toSave = forEntity.incrementVersion();
|
||||
|
||||
T toSave = (T) convertingAccessor.getBean();
|
||||
BeforeConvertEvent<T> event = new BeforeConvertEvent<>(toSave, collectionName);
|
||||
T afterEvent = ReactiveMongoTemplate.this.maybeEmitEvent(event).getSource();
|
||||
|
||||
ReactiveMongoTemplate.this.maybeEmitEvent(new BeforeConvertEvent<T>(toSave, collectionName));
|
||||
MappedDocument mapped = operations.forEntity(toSave).toMappedDocument(mongoConverter);
|
||||
Document document = mapped.getDocument();
|
||||
|
||||
Document document = ReactiveMongoTemplate.this.toDocument(toSave, mongoConverter);
|
||||
ReactiveMongoTemplate.this.maybeEmitEvent(new BeforeSaveEvent<>(afterEvent, document, collectionName));
|
||||
|
||||
ReactiveMongoTemplate.this.maybeEmitEvent(new BeforeSaveEvent<>(toSave, document, collectionName));
|
||||
Update update = Update.fromDocument(document, ID_FIELD);
|
||||
|
||||
return doUpdate(collectionName, query, update, toSave.getClass(), false, false).map(updateResult -> {
|
||||
|
||||
maybeEmitEvent(new AfterSaveEvent<>(toSave, document, collectionName));
|
||||
return toSave;
|
||||
});
|
||||
return doUpdate(collectionName, query, mapped.updateWithoutId(), afterEvent.getClass(), false, false)
|
||||
.map(updateResult -> maybeEmitEvent(new AfterSaveEvent<T>(afterEvent, document, collectionName)).getSource());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1460,15 +1473,16 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return createMono(collectionName, collection -> {
|
||||
|
||||
maybeEmitEvent(new BeforeConvertEvent<>(objectToSave, collectionName));
|
||||
Document dbDoc = toDocument(objectToSave, writer);
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(objectToSave, dbDoc, collectionName));
|
||||
T toSave = maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave, collectionName)).getSource();
|
||||
|
||||
return saveDocument(collectionName, dbDoc, objectToSave.getClass()).map(id -> {
|
||||
AdaptibleEntity<T> entity = operations.forEntity(toSave, mongoConverter.getConversionService());
|
||||
Document dbDoc = entity.toMappedDocument(writer).getDocument();
|
||||
maybeEmitEvent(new BeforeSaveEvent<T>(toSave, dbDoc, collectionName));
|
||||
|
||||
T saved = (T) populateIdIfNecessary(objectToSave, id);
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName));
|
||||
return saved;
|
||||
return saveDocument(collectionName, dbDoc, toSave.getClass()).map(id -> {
|
||||
|
||||
T saved = entity.populateIdIfNecessary(id);
|
||||
return maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName)).getSource();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1479,7 +1493,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
LOGGER.debug("Inserting Document containing fields: " + dbDoc.keySet() + " in collection: " + collectionName);
|
||||
}
|
||||
|
||||
final Document document = new Document(dbDoc);
|
||||
Document document = new Document(dbDoc);
|
||||
|
||||
Flux<Success> execute = execute(collectionName, collection -> {
|
||||
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, entityClass,
|
||||
@@ -1491,7 +1506,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return collectionToUse.insertOne(document);
|
||||
});
|
||||
|
||||
return Flux.from(execute).last().map(success -> document.get(ID_FIELD));
|
||||
return Flux.from(execute).last().map(success -> MappedDocument.of(document).getId());
|
||||
}
|
||||
|
||||
protected Flux<ObjectId> insertDocumentList(final String collectionName, final List<Document> dbDocList) {
|
||||
@@ -1516,12 +1531,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
documents.addAll(toDocuments(dbDocList));
|
||||
|
||||
return collectionToUse.insertMany(documents);
|
||||
|
||||
}).flatMap(s -> {
|
||||
|
||||
List<Document> documentsWithIds = documents.stream()
|
||||
.filter(document -> document.get(ID_FIELD) instanceof ObjectId).collect(Collectors.toList());
|
||||
return Flux.fromIterable(documentsWithIds);
|
||||
}).map(document -> document.get(ID_FIELD, ObjectId.class));
|
||||
return Flux.fromStream(documents.stream() //
|
||||
.map(MappedDocument::of) //
|
||||
.filter(it -> it.isIdPresent(ObjectId.class)) //
|
||||
.map(it -> it.getId(ObjectId.class)));
|
||||
});
|
||||
}
|
||||
|
||||
private MongoCollection<Document> prepareCollection(MongoCollection<Document> collection,
|
||||
@@ -1546,24 +1563,19 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass,
|
||||
document, null);
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
MappedDocument mapped = MappedDocument.of(document);
|
||||
|
||||
Publisher<?> publisher;
|
||||
if (!document.containsKey(ID_FIELD)) {
|
||||
if (writeConcernToUse == null) {
|
||||
publisher = collection.insertOne(document);
|
||||
} else {
|
||||
publisher = collection.withWriteConcern(writeConcernToUse).insertOne(document);
|
||||
}
|
||||
} else if (writeConcernToUse == null) {
|
||||
publisher = collection.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document,
|
||||
new ReplaceOptions().upsert(true));
|
||||
} else {
|
||||
publisher = collection.withWriteConcern(writeConcernToUse)
|
||||
.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document, new ReplaceOptions().upsert(true));
|
||||
}
|
||||
MongoCollection<Document> collectionToUse = writeConcernToUse == null //
|
||||
? collection //
|
||||
: collection.withWriteConcern(writeConcernToUse);
|
||||
|
||||
return Mono.from(publisher).map(o -> document.get(ID_FIELD));
|
||||
Publisher<?> publisher = !mapped.hasId() //
|
||||
? collectionToUse.insertOne(document) //
|
||||
: collectionToUse.replaceOne(mapped.getIdFilter(), document, new ReplaceOptions().upsert(true));
|
||||
|
||||
return Mono.from(publisher).map(o -> mapped.getId());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1639,7 +1651,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return doUpdate(collectionName, query, update, entityClass, false, true);
|
||||
}
|
||||
|
||||
protected Mono<UpdateResult> doUpdate(final String collectionName, @Nullable Query query, @Nullable Update update,
|
||||
protected Mono<UpdateResult> doUpdate(final String collectionName, Query query, @Nullable Update update,
|
||||
@Nullable Class<?> entityClass, final boolean upsert, final boolean multi) {
|
||||
|
||||
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
|
||||
@@ -1648,7 +1660,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
|
||||
Document queryObj = query == null ? new Document() : queryMapper.getMappedObject(query.getQueryObject(), entity);
|
||||
Document queryObj = queryMapper.getMappedObject(query.getQueryObject(), entity);
|
||||
Document updateObj = update == null ? new Document()
|
||||
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
|
||||
@@ -1742,7 +1754,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(object, "Object must not be null!");
|
||||
|
||||
return remove(getIdQueryFor(object), object.getClass());
|
||||
return remove(operations.forEntity(object).getByIdQuery(), object.getClass());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1754,72 +1766,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Assert.notNull(object, "Object must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
|
||||
return doRemove(collectionName, getIdQueryFor(object), object.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s
|
||||
* property value as its {@link Entry#getValue()}.
|
||||
*
|
||||
* @param object
|
||||
* @return
|
||||
*/
|
||||
private Pair<String, Object> extractIdPropertyAndValue(Object object) {
|
||||
|
||||
Assert.notNull(object, "Id cannot be extracted from 'null'.");
|
||||
|
||||
Assert.notNull(object, "Id cannot be extracted from 'null'.");
|
||||
|
||||
Class<?> objectType = object.getClass();
|
||||
|
||||
if (object instanceof Document) {
|
||||
return Pair.of(ID_FIELD, ((Document) object).get(ID_FIELD));
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(objectType);
|
||||
|
||||
if (entity != null && entity.hasIdProperty()) {
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
return Pair.of(idProperty.getFieldName(), entity.getPropertyAccessor(object).getProperty(idProperty));
|
||||
}
|
||||
|
||||
throw new MappingException("No id property found for object of type " + objectType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Query} for the given entity by its id.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private Query getIdQueryFor(Object object) {
|
||||
|
||||
Pair<String, Object> id = extractIdPropertyAndValue(object);
|
||||
return new Query(where(id.getFirst()).is(id.getSecond()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Query} for the given entities by their ids.
|
||||
*
|
||||
* @param objects must not be {@literal null} or {@literal empty}.
|
||||
* @return
|
||||
*/
|
||||
private Query getIdInQueryFor(Collection<?> objects) {
|
||||
|
||||
Assert.notEmpty(objects, "Cannot create Query for empty collection.");
|
||||
|
||||
Iterator<?> it = objects.iterator();
|
||||
Pair<String, Object> firstEntry = extractIdPropertyAndValue(it.next());
|
||||
|
||||
ArrayList<Object> ids = new ArrayList<>(objects.size());
|
||||
ids.add(firstEntry.getSecond());
|
||||
|
||||
while (it.hasNext()) {
|
||||
ids.add(extractIdPropertyAndValue(it.next()).getSecond());
|
||||
}
|
||||
|
||||
return new Query(where(firstEntry.getFirst()).in(ids));
|
||||
return doRemove(collectionName, operations.forEntity(object).getByIdQuery(), object.getClass());
|
||||
}
|
||||
|
||||
private void assertUpdateableIdIfNotSet(Object value) {
|
||||
@@ -1902,13 +1849,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
FindPublisher<Document> cursor = new QueryFindPublisherPreparer(query, entityClass)
|
||||
.prepare(collection.find(removeQuey)) //
|
||||
.projection(new Document(ID_FIELD, 1));
|
||||
.projection(MappedDocument.getIdOnlyProjection());
|
||||
|
||||
return Flux.from(cursor) //
|
||||
.map(doc -> doc.get(ID_FIELD)) //
|
||||
.map(MappedDocument::of) //
|
||||
.map(MappedDocument::getId) //
|
||||
.collectList() //
|
||||
.flatMapMany(val -> {
|
||||
return collectionToUse.deleteMany(new Document(ID_FIELD, new Document("$in", val)), deleteOptions);
|
||||
return collectionToUse.deleteMany(MappedDocument.getIdIn(val), deleteOptions);
|
||||
});
|
||||
} else {
|
||||
return collectionToUse.deleteMany(removeQuey, deleteOptions);
|
||||
@@ -2019,8 +1967,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
.map(publisher::startAtOperationTime).orElse(publisher);
|
||||
publisher = publisher.fullDocument(options.getFullDocumentLookup().orElse(fullDocument));
|
||||
|
||||
return Flux.from(publisher).map(document -> new ChangeStreamEvent<>(document, targetType, getConverter()));
|
||||
}
|
||||
return Flux.from(
|
||||
publisher ).map(document -> new ChangeStreamEvent<>(document, targetType, getConverter()));
|
||||
}
|
||||
|
||||
List<Document> prepareFilter(ChangeStreamOptions options) {
|
||||
|
||||
@@ -2223,7 +2172,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Flux<T> flux = find(query, entityClass, collectionName);
|
||||
|
||||
return Flux.from(flux).collectList()
|
||||
.flatMapMany(list -> Flux.from(remove(getIdInQueryFor(list), entityClass, collectionName))
|
||||
.flatMapMany(list -> Flux.from(remove(operations.getByIdInQuery(list), entityClass, collectionName))
|
||||
.flatMap(deleteResult -> Flux.fromIterable(list)));
|
||||
}
|
||||
|
||||
@@ -2509,50 +2458,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
});
|
||||
}
|
||||
|
||||
protected <T> void maybeEmitEvent(MongoMappingEvent<T> event) {
|
||||
protected <E extends MongoMappingEvent<T>, T> E maybeEmitEvent(E event) {
|
||||
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the id property of the saved object, if it's not set already.
|
||||
*
|
||||
* @param savedObject
|
||||
* @param id
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object populateIdIfNecessary(Object savedObject, @Nullable Object id) {
|
||||
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (savedObject instanceof Map) {
|
||||
|
||||
Map<String, Object> map = (Map<String, Object>) savedObject;
|
||||
map.put(ID_FIELD, id);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
MongoPersistentProperty idProp = getIdPropertyFor(savedObject.getClass());
|
||||
|
||||
if (idProp == null) {
|
||||
return savedObject;
|
||||
}
|
||||
|
||||
ConversionService conversionService = mongoConverter.getConversionService();
|
||||
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(savedObject.getClass());
|
||||
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(savedObject);
|
||||
|
||||
if (accessor.getProperty(idProp) != null) {
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, id);
|
||||
|
||||
return accessor.getBean();
|
||||
return event;
|
||||
}
|
||||
|
||||
private MongoCollection<Document> getAndPrepareCollection(MongoDatabase db, String collectionName) {
|
||||
@@ -2583,10 +2495,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @param collection
|
||||
*/
|
||||
protected MongoCollection<Document> prepareCollection(MongoCollection<Document> collection) {
|
||||
if (this.readPreference != null) {
|
||||
return collection.withReadPreference(readPreference);
|
||||
}
|
||||
return collection;
|
||||
return this.readPreference != null ? collection.withReadPreference(readPreference) : collection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2782,49 +2691,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param objectToSave
|
||||
* @param writer
|
||||
* @return
|
||||
*/
|
||||
private <T> Document toDocument(T objectToSave, MongoWriter<T> writer) {
|
||||
|
||||
if (objectToSave instanceof Document) {
|
||||
return (Document) objectToSave;
|
||||
}
|
||||
|
||||
if (!(objectToSave instanceof String)) {
|
||||
Document dbDoc = new Document();
|
||||
writer.write(objectToSave, dbDoc);
|
||||
|
||||
if (dbDoc.containsKey(ID_FIELD) && dbDoc.get(ID_FIELD) == null) {
|
||||
dbDoc.remove(ID_FIELD);
|
||||
}
|
||||
return dbDoc;
|
||||
} else {
|
||||
try {
|
||||
return Document.parse((String) objectToSave);
|
||||
} catch (JSONParseException | org.bson.json.JsonParseException e) {
|
||||
throw new MappingException("Could not parse given String to save into a JSON document!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object initializeVersionProperty(Object entity) {
|
||||
|
||||
MongoPersistentEntity<?> mongoPersistentEntity = getPersistentEntity(entity.getClass());
|
||||
|
||||
if (mongoPersistentEntity != null && mongoPersistentEntity.hasVersionProperty()) {
|
||||
ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(
|
||||
mongoPersistentEntity.getPropertyAccessor(entity), mongoConverter.getConversionService());
|
||||
accessor.setProperty(mongoPersistentEntity.getRequiredVersionProperty(), 0);
|
||||
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
// Callback implementations
|
||||
|
||||
/**
|
||||
@@ -3135,13 +3001,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final @NonNull String collectionName;
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public T doWith(@Nullable Document object) {
|
||||
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType
|
||||
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) //
|
||||
? entityType //
|
||||
: targetType;
|
||||
|
||||
if (null != object) {
|
||||
@@ -3208,6 +3076,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public <T> FindPublisher<T> prepare(FindPublisher<T> findPublisher) {
|
||||
|
||||
if (query == null) {
|
||||
@@ -3226,12 +3095,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (query.getSkip() > 0) {
|
||||
findPublisherToUse = findPublisherToUse.skip((int) query.getSkip());
|
||||
}
|
||||
|
||||
if (query.getLimit() > 0) {
|
||||
findPublisherToUse = findPublisherToUse.limit(query.getLimit());
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(query.getSortObject())) {
|
||||
Document sort = type != null ? getMappedSortObject(query, type) : query.getSortObject();
|
||||
findPublisherToUse = findPublisherToUse.sort(sort);
|
||||
|
||||
@@ -147,7 +147,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
public void setTypeMapper(@Nullable MongoTypeMapper typeMapper) {
|
||||
this.typeMapper = typeMapper == null
|
||||
? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext) : typeMapper;
|
||||
? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext)
|
||||
: typeMapper;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -272,7 +273,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
|
||||
S instance = instantiator.createInstance(entity, provider);
|
||||
|
||||
PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance),
|
||||
PersistentPropertyAccessor<S> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
|
||||
conversionService);
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
@@ -296,7 +297,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
MappingMongoConverter.this);
|
||||
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider, callback);
|
||||
|
||||
return (S) accessor.getBean();
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
private Object readIdValue(ObjectPath path, DefaultSpELExpressionEvaluator evaluator,
|
||||
@@ -557,7 +558,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass())
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass()) : mappingContext.getRequiredPersistentEntity(type);
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass())
|
||||
: mappingContext.getRequiredPersistentEntity(type);
|
||||
|
||||
Object existingValue = accessor.get(prop);
|
||||
Document document = existingValue instanceof Document ? (Document) existingValue : new Document();
|
||||
@@ -779,7 +781,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
return conversions.hasCustomWriteTarget(key.getClass(), String.class)
|
||||
? (String) getPotentiallyConvertedSimpleWrite(key) : key.toString();
|
||||
? (String) getPotentiallyConvertedSimpleWrite(key)
|
||||
: key.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1481,7 +1484,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
List<Document> referencedRawDocuments = dbrefs.size() == 1
|
||||
? Collections.singletonList(readRef(dbrefs.iterator().next())) : bulkReadRefs(dbrefs);
|
||||
? Collections.singletonList(readRef(dbrefs.iterator().next()))
|
||||
: bulkReadRefs(dbrefs);
|
||||
String collectionName = dbrefs.iterator().next().getCollectionName();
|
||||
|
||||
List<T> targeList = new ArrayList<>(dbrefs.size());
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
@@ -78,13 +77,13 @@ public class ExecutableAggregationOperationSupportUnitTests {
|
||||
@Test // DATAMONGO-1563
|
||||
public void aggregateWithUntypedAggregation() {
|
||||
|
||||
when(template.determineCollectionName(any(Class.class))).thenReturn("person");
|
||||
when(template.getCollectionName(any(Class.class))).thenReturn("person");
|
||||
|
||||
opSupport.aggregateAndReturn(Person.class).by(newAggregation(project("foo"))).all();
|
||||
|
||||
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
|
||||
verify(template).determineCollectionName(captor.capture());
|
||||
verify(template).getCollectionName(captor.capture());
|
||||
verify(template).aggregate(any(Aggregation.class), eq("person"), captor.capture());
|
||||
|
||||
assertThat(captor.getAllValues()).containsExactly(Person.class, Person.class);
|
||||
@@ -93,13 +92,13 @@ public class ExecutableAggregationOperationSupportUnitTests {
|
||||
@Test // DATAMONGO-1563
|
||||
public void aggregateWithTypeAggregation() {
|
||||
|
||||
when(template.determineCollectionName(any(Class.class))).thenReturn("person");
|
||||
when(template.getCollectionName(any(Class.class))).thenReturn("person");
|
||||
|
||||
opSupport.aggregateAndReturn(Jedi.class).by(newAggregation(Person.class, project("foo"))).all();
|
||||
|
||||
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
|
||||
verify(template).determineCollectionName(captor.capture());
|
||||
verify(template).getCollectionName(captor.capture());
|
||||
verify(template).aggregate(any(Aggregation.class), eq("person"), captor.capture());
|
||||
|
||||
assertThat(captor.getAllValues()).containsExactly(Person.class, Jedi.class);
|
||||
@@ -118,13 +117,13 @@ public class ExecutableAggregationOperationSupportUnitTests {
|
||||
@Test // DATAMONGO-1563
|
||||
public void aggregateStreamWithUntypedAggregation() {
|
||||
|
||||
when(template.determineCollectionName(any(Class.class))).thenReturn("person");
|
||||
when(template.getCollectionName(any(Class.class))).thenReturn("person");
|
||||
|
||||
opSupport.aggregateAndReturn(Person.class).by(newAggregation(project("foo"))).stream();
|
||||
|
||||
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
|
||||
verify(template).determineCollectionName(captor.capture());
|
||||
verify(template).getCollectionName(captor.capture());
|
||||
verify(template).aggregateStream(any(Aggregation.class), eq("person"), captor.capture());
|
||||
|
||||
assertThat(captor.getAllValues()).containsExactly(Person.class, Person.class);
|
||||
@@ -133,13 +132,13 @@ public class ExecutableAggregationOperationSupportUnitTests {
|
||||
@Test // DATAMONGO-1563
|
||||
public void aggregateStreamWithTypeAggregation() {
|
||||
|
||||
when(template.determineCollectionName(any(Class.class))).thenReturn("person");
|
||||
when(template.getCollectionName(any(Class.class))).thenReturn("person");
|
||||
|
||||
opSupport.aggregateAndReturn(Jedi.class).by(newAggregation(Person.class, project("foo"))).stream();
|
||||
|
||||
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
|
||||
verify(template).determineCollectionName(captor.capture());
|
||||
verify(template).getCollectionName(captor.capture());
|
||||
verify(template).aggregateStream(any(Aggregation.class), eq("person"), captor.capture());
|
||||
|
||||
assertThat(captor.getAllValues()).containsExactly(Person.class, Jedi.class);
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.anyList;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -56,7 +54,7 @@ public class ExecutableInsertOperationSupportUnitTests {
|
||||
public void setUp() {
|
||||
|
||||
when(template.bulkOps(any(), any(), any())).thenReturn(bulkOperations);
|
||||
when(template.determineCollectionName(any(Class.class))).thenReturn(STAR_WARS);
|
||||
when(template.getCollectionName(any(Class.class))).thenReturn(STAR_WARS);
|
||||
when(bulkOperations.insert(anyList())).thenReturn(bulkOperations);
|
||||
|
||||
ops = new ExecutableInsertOperationSupport(template);
|
||||
@@ -88,7 +86,7 @@ public class ExecutableInsertOperationSupportUnitTests {
|
||||
|
||||
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
|
||||
verify(template).determineCollectionName(captor.capture());
|
||||
verify(template).getCollectionName(captor.capture());
|
||||
verify(template).insert(eq(luke), eq(STAR_WARS));
|
||||
|
||||
assertThat(captor.getAllValues()).containsExactly(Person.class);
|
||||
@@ -99,7 +97,7 @@ public class ExecutableInsertOperationSupportUnitTests {
|
||||
|
||||
ops.insert(Person.class).inCollection(STAR_WARS).one(luke);
|
||||
|
||||
verify(template, never()).determineCollectionName(any(Class.class));
|
||||
verify(template, never()).getCollectionName(any(Class.class));
|
||||
verify(template).insert(eq(luke), eq(STAR_WARS));
|
||||
}
|
||||
|
||||
@@ -108,7 +106,7 @@ public class ExecutableInsertOperationSupportUnitTests {
|
||||
|
||||
ops.insert(Person.class).all(Arrays.asList(luke, han));
|
||||
|
||||
verify(template).determineCollectionName(any(Class.class));
|
||||
verify(template).getCollectionName(any(Class.class));
|
||||
verify(template).insert(anyList(), eq(STAR_WARS));
|
||||
}
|
||||
|
||||
@@ -119,7 +117,7 @@ public class ExecutableInsertOperationSupportUnitTests {
|
||||
|
||||
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
|
||||
verify(template).determineCollectionName(any(Class.class));
|
||||
verify(template).getCollectionName(any(Class.class));
|
||||
verify(template).bulkOps(eq(BulkMode.ORDERED), captor.capture(), eq(STAR_WARS));
|
||||
verify(bulkOperations).insert(anyList());
|
||||
verify(bulkOperations).execute();
|
||||
@@ -132,7 +130,7 @@ public class ExecutableInsertOperationSupportUnitTests {
|
||||
|
||||
ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
|
||||
|
||||
verify(template).determineCollectionName(any(Class.class));
|
||||
verify(template).getCollectionName(any(Class.class));
|
||||
verify(template).bulkOps(eq(BulkMode.UNORDERED), captor.capture(), eq(STAR_WARS));
|
||||
verify(bulkOperations).insert(anyList());
|
||||
verify(bulkOperations).execute();
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -55,7 +53,7 @@ public class ExecutableMapReduceOperationSupportUnitTests {
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(template.determineCollectionName(eq(Person.class))).thenReturn(STAR_WARS);
|
||||
when(template.getCollectionName(eq(Person.class))).thenReturn(STAR_WARS);
|
||||
|
||||
mapReduceOpsSupport = new ExecutableMapReduceOperationSupport(template);
|
||||
}
|
||||
|
||||
@@ -30,16 +30,28 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.Wither;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import lombok.experimental.Wither;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.hamcrest.collection.IsMapContaining;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -58,8 +70,10 @@ import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -79,6 +93,7 @@ import org.springframework.data.mongodb.core.index.IndexInfo;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AuditingEventListener;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
@@ -140,6 +155,8 @@ public class MongoTemplateTests {
|
||||
this.context = context;
|
||||
|
||||
context.addApplicationListener(new PersonWithIdPropertyOfTypeUUIDListener());
|
||||
context.addApplicationListener(
|
||||
new AuditingEventListener(() -> new IsNewAwareAuditingHandler(template.getConverter().getMappingContext())));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@@ -219,6 +236,7 @@ public class MongoTemplateTests {
|
||||
template.dropCollection(DocumentWithCollectionOfSamples.class);
|
||||
template.dropCollection(WithGeoJson.class);
|
||||
template.dropCollection(DocumentWithNestedTypeHavingStringIdProperty.class);
|
||||
template.dropCollection(ImmutableAudited.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -353,7 +371,6 @@ public class MongoTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testEnsureIndex() throws Exception {
|
||||
|
||||
Person p1 = new Person("Oliver");
|
||||
@@ -762,7 +779,7 @@ public class MongoTemplateTests {
|
||||
assertThat(template.findDistinct(new BasicQuery("{'address.state' : 'PA'}"), "name", MyPerson.class, String.class))
|
||||
.containsExactlyInAnyOrder(person1.getName(), person2.getName());
|
||||
assertThat(template.findDistinct(new BasicQuery("{'address.state' : 'PA'}"), "name",
|
||||
template.determineCollectionName(MyPerson.class), MyPerson.class, String.class))
|
||||
template.getCollectionName(MyPerson.class), MyPerson.class, String.class))
|
||||
.containsExactlyInAnyOrder(person1.getName(), person2.getName());
|
||||
}
|
||||
|
||||
@@ -1259,14 +1276,14 @@ public class MongoTemplateTests {
|
||||
|
||||
template.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
|
||||
template.save(person);
|
||||
UpdateResult result = template.updateFirst(query(where("id").is(person.getId())), update("firstName", "Carter"),
|
||||
template.updateFirst(query(where("id").is(person.getId())), update("firstName", "Carter"),
|
||||
PersonWithIdPropertyOfTypeObjectId.class);
|
||||
|
||||
FsyncSafeWriteConcernResolver resolver = new FsyncSafeWriteConcernResolver();
|
||||
template.setWriteConcernResolver(resolver);
|
||||
Query q = query(where("_id").is(person.getId()));
|
||||
Update u = update("firstName", "Carter");
|
||||
result = template.updateFirst(q, u, PersonWithIdPropertyOfTypeObjectId.class);
|
||||
template.updateFirst(q, u, PersonWithIdPropertyOfTypeObjectId.class);
|
||||
|
||||
MongoAction lastMongoAction = resolver.getMongoAction();
|
||||
assertThat(lastMongoAction.getCollectionName(), is("personWithIdPropertyOfTypeObjectId"));
|
||||
@@ -1283,7 +1300,7 @@ public class MongoTemplateTests {
|
||||
|
||||
public WriteConcern resolve(MongoAction action) {
|
||||
this.mongoAction = action;
|
||||
return WriteConcern.FSYNC_SAFE;
|
||||
return WriteConcern.JOURNALED;
|
||||
}
|
||||
|
||||
public MongoAction getMongoAction() {
|
||||
@@ -1530,7 +1547,7 @@ public class MongoTemplateTests {
|
||||
org.bson.Document document = new org.bson.Document();
|
||||
document.put("firstName", "Oliver");
|
||||
|
||||
template.insert(document, template.determineCollectionName(PersonWithVersionPropertyOfTypeInteger.class));
|
||||
template.insert(document, template.getCollectionName(PersonWithVersionPropertyOfTypeInteger.class));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1617
|
||||
@@ -1685,7 +1702,7 @@ public class MongoTemplateTests {
|
||||
@Test(expected = DuplicateKeyException.class) // DATAMONGO-622
|
||||
public void preventsDuplicateInsert() {
|
||||
|
||||
template.setWriteConcern(WriteConcern.SAFE);
|
||||
template.setWriteConcern(WriteConcern.ACKNOWLEDGED);
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.firstName = "Dave";
|
||||
@@ -3527,11 +3544,11 @@ public class MongoTemplateTests {
|
||||
template.save(rickon);
|
||||
|
||||
List<Sample> result = template.findAllAndRemove(query(where("field").regex(".*stark$")),
|
||||
template.determineCollectionName(Sample.class));
|
||||
template.getCollectionName(Sample.class));
|
||||
|
||||
assertThat(result, hasSize(2));
|
||||
assertThat(result, containsInAnyOrder(bran, rickon));
|
||||
assertThat(template.count(new BasicQuery("{}"), template.determineCollectionName(Sample.class)), is(equalTo(1L)));
|
||||
assertThat(template.count(new BasicQuery("{}"), template.getCollectionName(Sample.class)), is(equalTo(1L)));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1779
|
||||
@@ -3594,6 +3611,20 @@ public class MongoTemplateTests {
|
||||
assertThat(target).isEqualTo(source);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1992
|
||||
public void writesAuditingMetadataForImmutableTypes() {
|
||||
|
||||
ImmutableAudited source = new ImmutableAudited(null, null);
|
||||
ImmutableAudited result = template.save(source);
|
||||
|
||||
assertThat(result).isNotSameAs(source).describedAs("Expected a different instances to be returned!");
|
||||
assertThat(result.modified).isNotNull().describedAs("Auditing field must not be null!");
|
||||
|
||||
ImmutableAudited read = template.findOne(query(where("id").is(result.getId())), ImmutableAudited.class);
|
||||
|
||||
assertThat(read.modified).isEqualTo(result.modified).describedAs("Expected auditing information to be read!");
|
||||
}
|
||||
|
||||
static class TypeWithNumbers {
|
||||
|
||||
@Id String id;
|
||||
@@ -4074,6 +4105,8 @@ public class MongoTemplateTests {
|
||||
|
||||
}
|
||||
|
||||
// DATAMONGO-1992
|
||||
|
||||
@AllArgsConstructor
|
||||
@Wither
|
||||
static class ImmutableVersioned {
|
||||
@@ -4086,4 +4119,11 @@ public class MongoTemplateTests {
|
||||
version = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
@Wither
|
||||
static class ImmutableAudited {
|
||||
@Id String id;
|
||||
@LastModifiedDate Instant modified;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,25 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping.event;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.Wither;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.AdditionalAnswers;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
@@ -54,8 +62,9 @@ public class AuditingEventListenerUnitTests {
|
||||
mappingContext.getPersistentEntity(Sample.class);
|
||||
|
||||
handler = spy(new IsNewAwareAuditingHandler(new PersistentEntities(Arrays.asList(mappingContext))));
|
||||
doNothing().when(handler).markCreated(any());
|
||||
doNothing().when(handler).markModified(any());
|
||||
|
||||
doAnswer(AdditionalAnswers.returnsArgAt(0)).when(handler).markCreated(any());
|
||||
doAnswer(AdditionalAnswers.returnsArgAt(0)).when(handler).markModified(any());
|
||||
|
||||
listener = new AuditingEventListener(() -> handler);
|
||||
}
|
||||
@@ -93,10 +102,37 @@ public class AuditingEventListenerUnitTests {
|
||||
assertThat(listener.getOrder(), is(100));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1992
|
||||
public void propagatesChangedInstanceToEvent() {
|
||||
|
||||
ImmutableSample sample = new ImmutableSample();
|
||||
BeforeConvertEvent<Object> event = new BeforeConvertEvent<>(sample, "collection");
|
||||
|
||||
ImmutableSample newSample = new ImmutableSample();
|
||||
IsNewAwareAuditingHandler handler = mock(IsNewAwareAuditingHandler.class);
|
||||
doReturn(newSample).when(handler).markAudited(eq(sample));
|
||||
|
||||
AuditingEventListener listener = new AuditingEventListener(() -> handler);
|
||||
listener.onApplicationEvent(event);
|
||||
|
||||
assertThat(event.getSource()).isSameAs(newSample);
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
|
||||
@Id String id;
|
||||
@CreatedDate Date created;
|
||||
@LastModifiedDate Date modified;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Wither
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor(force = true)
|
||||
static class ImmutableSample {
|
||||
|
||||
@Id String id;
|
||||
@CreatedDate Date created;
|
||||
@LastModifiedDate Date modified;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user