Browse Source
Extract common code into BulkOperationsSupport. Reorder methods. Add missing verifyComplete to tests. See #2821 Original pull request: #4342pull/4373/head
8 changed files with 425 additions and 504 deletions
@ -0,0 +1,221 @@
@@ -0,0 +1,221 @@
|
||||
/* |
||||
* Copyright 2023 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; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.bson.Document; |
||||
import org.bson.conversions.Bson; |
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.data.mapping.PersistentEntity; |
||||
import org.springframework.data.mongodb.core.BulkOperations.BulkMode; |
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; |
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate; |
||||
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext; |
||||
import org.springframework.data.mongodb.core.convert.QueryMapper; |
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper; |
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; |
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent; |
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent; |
||||
import org.springframework.data.mongodb.core.query.Collation; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
import org.springframework.data.mongodb.core.query.Update; |
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition; |
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import com.mongodb.client.model.BulkWriteOptions; |
||||
import com.mongodb.client.model.DeleteManyModel; |
||||
import com.mongodb.client.model.DeleteOneModel; |
||||
import com.mongodb.client.model.InsertOneModel; |
||||
import com.mongodb.client.model.ReplaceOneModel; |
||||
import com.mongodb.client.model.UpdateManyModel; |
||||
import com.mongodb.client.model.UpdateOneModel; |
||||
import com.mongodb.client.model.UpdateOptions; |
||||
import com.mongodb.client.model.WriteModel; |
||||
|
||||
/** |
||||
* Support class for bulk operations. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 4.1 |
||||
*/ |
||||
abstract class BulkOperationsSupport { |
||||
|
||||
private final String collectionName; |
||||
|
||||
BulkOperationsSupport(String collectionName) { |
||||
|
||||
Assert.hasText(collectionName, "CollectionName must not be null nor empty"); |
||||
|
||||
this.collectionName = collectionName; |
||||
} |
||||
|
||||
/** |
||||
* Emit a {@link BeforeSaveEvent}. |
||||
* |
||||
* @param holder |
||||
*/ |
||||
void maybeEmitBeforeSaveEvent(SourceAwareWriteModelHolder holder) { |
||||
|
||||
if (holder.model() instanceof InsertOneModel) { |
||||
|
||||
Document target = ((InsertOneModel<Document>) holder.model()).getDocument(); |
||||
maybeEmitEvent(new BeforeSaveEvent<>(holder.source(), target, collectionName)); |
||||
} else if (holder.model() instanceof ReplaceOneModel) { |
||||
|
||||
Document target = ((ReplaceOneModel<Document>) holder.model()).getReplacement(); |
||||
maybeEmitEvent(new BeforeSaveEvent<>(holder.source(), target, collectionName)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Emit a {@link AfterSaveEvent}. |
||||
* |
||||
* @param holder |
||||
*/ |
||||
void maybeEmitAfterSaveEvent(SourceAwareWriteModelHolder holder) { |
||||
|
||||
if (holder.model() instanceof InsertOneModel) { |
||||
|
||||
Document target = ((InsertOneModel<Document>) holder.model()).getDocument(); |
||||
maybeEmitEvent(new AfterSaveEvent<>(holder.source(), target, collectionName)); |
||||
} else if (holder.model() instanceof ReplaceOneModel) { |
||||
|
||||
Document target = ((ReplaceOneModel<Document>) holder.model()).getReplacement(); |
||||
maybeEmitEvent(new AfterSaveEvent<>(holder.source(), target, collectionName)); |
||||
} |
||||
} |
||||
|
||||
WriteModel<Document> mapWriteModel(Object source, WriteModel<Document> writeModel) { |
||||
|
||||
if (writeModel instanceof UpdateOneModel<Document> model) { |
||||
|
||||
if (source instanceof AggregationUpdate aggregationUpdate) { |
||||
|
||||
List<Document> pipeline = mapUpdatePipeline(aggregationUpdate); |
||||
return new UpdateOneModel<>(getMappedQuery(model.getFilter()), pipeline, model.getOptions()); |
||||
} |
||||
|
||||
return new UpdateOneModel<>(getMappedQuery(model.getFilter()), getMappedUpdate(model.getUpdate()), |
||||
model.getOptions()); |
||||
} |
||||
|
||||
if (writeModel instanceof UpdateManyModel<Document> model) { |
||||
|
||||
if (source instanceof AggregationUpdate aggregationUpdate) { |
||||
|
||||
List<Document> pipeline = mapUpdatePipeline(aggregationUpdate); |
||||
return new UpdateManyModel<>(getMappedQuery(model.getFilter()), pipeline, model.getOptions()); |
||||
} |
||||
|
||||
return new UpdateManyModel<>(getMappedQuery(model.getFilter()), getMappedUpdate(model.getUpdate()), |
||||
model.getOptions()); |
||||
} |
||||
|
||||
if (writeModel instanceof DeleteOneModel<Document> model) { |
||||
return new DeleteOneModel<>(getMappedQuery(model.getFilter()), model.getOptions()); |
||||
} |
||||
|
||||
if (writeModel instanceof DeleteManyModel<Document> model) { |
||||
return new DeleteManyModel<>(getMappedQuery(model.getFilter()), model.getOptions()); |
||||
} |
||||
|
||||
return writeModel; |
||||
} |
||||
|
||||
private List<Document> mapUpdatePipeline(AggregationUpdate source) { |
||||
|
||||
Class<?> type = entity().isPresent() ? entity().map(PersistentEntity::getType).get() : Object.class; |
||||
AggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(type, |
||||
updateMapper().getMappingContext(), queryMapper()); |
||||
|
||||
return new AggregationUtil(queryMapper(), queryMapper().getMappingContext()).createPipeline(source, context); |
||||
} |
||||
|
||||
/** |
||||
* Emit a {@link ApplicationEvent} if event multicasting is enabled. |
||||
* |
||||
* @param event |
||||
*/ |
||||
protected abstract void maybeEmitEvent(ApplicationEvent event); |
||||
|
||||
/** |
||||
* @return the {@link UpdateMapper} to use. |
||||
*/ |
||||
protected abstract UpdateMapper updateMapper(); |
||||
|
||||
/** |
||||
* @return the {@link QueryMapper} to use. |
||||
*/ |
||||
protected abstract QueryMapper queryMapper(); |
||||
|
||||
/** |
||||
* @return the associated {@link PersistentEntity}. Can be {@link Optional#empty()}. |
||||
*/ |
||||
protected abstract Optional<? extends MongoPersistentEntity<?>> entity(); |
||||
|
||||
protected Bson getMappedUpdate(Bson update) { |
||||
return updateMapper().getMappedObject(update, entity()); |
||||
} |
||||
|
||||
protected Bson getMappedQuery(Bson query) { |
||||
return queryMapper().getMappedObject(query, entity()); |
||||
} |
||||
|
||||
protected static BulkWriteOptions getBulkWriteOptions(BulkMode bulkMode) { |
||||
|
||||
BulkWriteOptions options = new BulkWriteOptions(); |
||||
|
||||
return switch (bulkMode) { |
||||
case ORDERED -> options.ordered(true); |
||||
case UNORDERED -> options.ordered(false); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* @param filterQuery The {@link Query} to read a potential {@link Collation} from. Must not be {@literal null}. |
||||
* @param update The {@link Update} to apply |
||||
* @param upsert flag to indicate if document should be upserted. |
||||
* @return new instance of {@link UpdateOptions}. |
||||
*/ |
||||
protected static UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefinition update, boolean upsert) { |
||||
|
||||
UpdateOptions options = new UpdateOptions(); |
||||
options.upsert(upsert); |
||||
|
||||
if (update.hasArrayFilters()) { |
||||
List<Document> list = new ArrayList<>(update.getArrayFilters().size()); |
||||
for (ArrayFilter arrayFilter : update.getArrayFilters()) { |
||||
list.add(arrayFilter.asDocument()); |
||||
} |
||||
options.arrayFilters(list); |
||||
} |
||||
|
||||
filterQuery.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation); |
||||
return options; |
||||
} |
||||
|
||||
/** |
||||
* Value object chaining together an actual source with its {@link WriteModel} representation. |
||||
* |
||||
* @author Christoph Strobl |
||||
*/ |
||||
record SourceAwareWriteModelHolder(Object source, WriteModel<Document> model) { |
||||
} |
||||
} |
||||
Loading…
Reference in new issue