diff --git a/pom.xml b/pom.xml index 5d28c8a5c5..05b1248974 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0-3760-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index 0033bd11d5..e080c92b80 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0-3760-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index f62c8dc7f4..70aa8e367b 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0-3760-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 1f157e75bc..5ae9932843 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0-3760-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java index ba8efa536c..c9342ec4f6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java @@ -104,7 +104,8 @@ private static MongoDatabase doGetMongoDatabase(@Nullable String dbName, MongoDa Assert.notNull(factory, "Factory must not be null!"); - if (!TransactionSynchronizationManager.isSynchronizationActive()) { + if (sessionSynchronization == SessionSynchronization.NEVER + || !TransactionSynchronizationManager.isSynchronizationActive()) { return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java index 711947a30d..4699ac56c2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java @@ -138,6 +138,10 @@ private static Mono doGetMongoDatabase(@Nullable String dbName, R Assert.notNull(factory, "DatabaseFactory must not be null!"); + if (sessionSynchronization == SessionSynchronization.NEVER) { + return getMongoDatabaseOrDefault(dbName, factory); + } + return TransactionSynchronizationManager.forCurrentTransaction() .filter(TransactionSynchronizationManager::isSynchronizationActive) // .flatMap(synchronizationManager -> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionSynchronization.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionSynchronization.java index 2223b82391..144d3d3cb3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionSynchronization.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionSynchronization.java @@ -15,13 +15,20 @@ */ package org.springframework.data.mongodb; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; + /** - * {@link SessionSynchronization} is used along with {@link org.springframework.data.mongodb.core.MongoTemplate} to - * define in which type of transactions to participate if any. + * {@link SessionSynchronization} is used along with {@code MongoTemplate} to define in which type of transactions to + * participate if any. * * @author Christoph Strobl * @author Mark Paluch * @since 2.1 + * @see MongoTemplate#setSessionSynchronization(SessionSynchronization) + * @see MongoDatabaseUtils#getDatabase(MongoDatabaseFactory, SessionSynchronization) + * @see ReactiveMongoTemplate#setSessionSynchronization(SessionSynchronization) + * @see ReactiveMongoDatabaseUtils#getDatabase(ReactiveMongoDatabaseFactory, SessionSynchronization) */ public enum SessionSynchronization { @@ -34,5 +41,12 @@ public enum SessionSynchronization { /** * Synchronize with native MongoDB transactions initiated via {@link MongoTransactionManager}. */ - ON_ACTUAL_TRANSACTION; + ON_ACTUAL_TRANSACTION, + + /** + * Do not participate in ongoing transactions. + * + * @since 3.2.5 + */ + NEVER; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappedDocument.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappedDocument.java index 340c11bb99..e3c1f3d64c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappedDocument.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappedDocument.java @@ -156,5 +156,14 @@ public Boolean isIsolated() { public List getArrayFilters() { return delegate.getArrayFilters(); } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.UpdateDefinition#hasArrayFilters() + */ + @Override + public boolean hasArrayFilters() { + return delegate.hasArrayFilters(); + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java index 1ec8fc9366..e9431aa3d2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java @@ -613,7 +613,7 @@ class UpdateContext extends QueryContext { UpdateContext(MappedDocument update, boolean upsert) { - super(new BasicQuery(new Document(BsonUtils.asMap(update.getIdFilter())))); + super(new BasicQuery(BsonUtils.asDocument(update.getIdFilter()))); this.multi = false; this.upsert = upsert; this.mappedDocument = update; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java index 9c94487a3e..0b31f75341 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentAccessor.java @@ -135,7 +135,7 @@ public Object get(MongoPersistentProperty property) { */ @Nullable public Object getRawId(MongoPersistentEntity entity) { - return entity.hasIdProperty() ? get(entity.getRequiredIdProperty()) : BsonUtils.asMap(document).get("_id"); + return entity.hasIdProperty() ? get(entity.getRequiredIdProperty()) : BsonUtils.get(document, "_id"); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index a60c853c33..66217bb0ec 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -25,7 +25,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -1316,21 +1315,22 @@ protected Map readMap(ConversionContext context, Bson bson, Type return map; } - for (Entry entry : sourceMap.entrySet()) { + sourceMap.forEach((k, v) -> { - if (typeMapper.isTypeKey(entry.getKey())) { - continue; + if (typeMapper.isTypeKey(k)) { + return; } - Object key = potentiallyUnescapeMapKey(entry.getKey()); + Object key = potentiallyUnescapeMapKey(k); if (!rawKeyType.isAssignableFrom(key.getClass())) { key = doConvert(key, rawKeyType); } - Object value = entry.getValue(); + Object value = v; map.put(key, value == null ? value : context.convert(value, valueType)); - } + + }); return map; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java index 20499d3173..aff1b8d8e0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java @@ -140,6 +140,9 @@ default Object convertId(@Nullable Object id, Class targetType) { if (ObjectId.isValid(id.toString())) { return new ObjectId(id.toString()); } + + // avoid ConversionException as convertToMongoType will return String anyways. + return id; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index e7deb38231..356dd89faa 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -193,12 +193,11 @@ public Document getMappedSort(Document sortObject, @Nullable MongoPersistentEnti Assert.notNull(sortObject, "SortObject must not be null!"); if (sortObject.isEmpty()) { - return new Document(); + return BsonUtils.EMPTY_DOCUMENT; } Document mappedSort = mapFieldsToPropertyNames(sortObject, entity); - mapMetaAttributes(mappedSort, entity, MetaMapping.WHEN_PRESENT); - return mappedSort; + return mapMetaAttributes(mappedSort, entity, MetaMapping.WHEN_PRESENT); } /** @@ -215,42 +214,51 @@ public Document getMappedFields(Document fieldsObject, @Nullable MongoPersistent Assert.notNull(fieldsObject, "FieldsObject must not be null!"); Document mappedFields = mapFieldsToPropertyNames(fieldsObject, entity); - mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE); - return mappedFields; + return mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE); } private Document mapFieldsToPropertyNames(Document fields, @Nullable MongoPersistentEntity entity) { if (fields.isEmpty()) { - return new Document(); + return BsonUtils.EMPTY_DOCUMENT; } Document target = new Document(); - for (Map.Entry entry : BsonUtils.asMap(filterUnwrappedObjects(fields, entity)).entrySet()) { - Field field = createPropertyField(entity, entry.getKey(), mappingContext); + BsonUtils.asMap(filterUnwrappedObjects(fields, entity)).forEach((k, v) -> { + + Field field = createPropertyField(entity, k, mappingContext); if (field.getProperty() != null && field.getProperty().isUnwrapped()) { - continue; + return; } - target.put(field.getMappedKey(), entry.getValue()); - } + target.put(field.getMappedKey(), v); + }); + return target; } - private void mapMetaAttributes(Document source, @Nullable MongoPersistentEntity entity, MetaMapping metaMapping) { + private Document mapMetaAttributes(Document source, @Nullable MongoPersistentEntity entity, + MetaMapping metaMapping) { if (entity == null) { - return; + return source; } if (entity.hasTextScoreProperty() && !MetaMapping.IGNORE.equals(metaMapping)) { + + if (source == BsonUtils.EMPTY_DOCUMENT) { + source = new Document(); + } + MongoPersistentProperty textScoreProperty = entity.getTextScoreProperty(); if (MetaMapping.FORCE.equals(metaMapping) || (MetaMapping.WHEN_PRESENT.equals(metaMapping) && source.containsKey(textScoreProperty.getFieldName()))) { source.putAll(getMappedTextScoreField(textScoreProperty)); } } + + return source; } private Document filterUnwrappedObjects(Document fieldsObject, @Nullable MongoPersistentEntity entity) { @@ -679,7 +687,7 @@ protected final Entry createMapEntry(Field field, @Nullable Obje private Entry createMapEntry(String key, @Nullable Object value) { Assert.hasText(key, "Key must not be null or empty!"); - return Collections.singletonMap(key, value).entrySet().iterator().next(); + return new AbstractMap.SimpleEntry<>(key, value); } private Object createReferenceFor(Object source, MongoPersistentProperty property) { @@ -733,13 +741,13 @@ protected boolean isNestedKeyword(@Nullable Object candidate) { return false; } - Set keys = BsonUtils.asMap((Bson) candidate).keySet(); + Map map = BsonUtils.asMap((Bson) candidate); - if (keys.size() != 1) { + if (map.size() != 1) { return false; } - return isKeyword(keys.iterator().next()); + return isKeyword(map.entrySet().iterator().next().getKey()); } /** @@ -823,11 +831,14 @@ public Keyword(Bson source, String key) { public Keyword(Bson bson) { - Set keys = BsonUtils.asMap(bson).keySet(); - Assert.isTrue(keys.size() == 1, "Can only use a single value Document!"); + Map map = BsonUtils.asMap(bson); + Assert.isTrue(map.size() == 1, "Can only use a single value Document!"); + + Set> entries = map.entrySet(); + Entry entry = entries.iterator().next(); - this.key = keys.iterator().next(); - this.value = BsonUtils.get(bson, key); + this.key = entry.getKey(); + this.value = entry.getValue(); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java index 2bfddfa2cd..d70a21707f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java @@ -49,8 +49,8 @@ private enum MetaKey { } } - private final Map values = new LinkedHashMap<>(2); - private final Set flags = new LinkedHashSet<>(); + private Map values = Collections.emptyMap(); + private Set flags = Collections.emptySet(); private Integer cursorBatchSize; private Boolean allowDiskUse; @@ -63,8 +63,9 @@ public Meta() {} * @param source */ Meta(Meta source) { - this.values.putAll(source.values); - this.flags.addAll(source.flags); + + this.values = new LinkedHashMap<>(source.values); + this.flags = new LinkedHashSet<>(source.flags); this.cursorBatchSize = source.cursorBatchSize; this.allowDiskUse = source.allowDiskUse; } @@ -158,6 +159,11 @@ public void setCursorBatchSize(int cursorBatchSize) { public boolean addFlag(CursorOption option) { Assert.notNull(option, "CursorOption must not be null!"); + + if (this.flags == Collections.EMPTY_SET) { + this.flags = new LinkedHashSet<>(2); + } + return this.flags.add(option); } @@ -220,6 +226,10 @@ void setValue(String key, @Nullable Object value) { Assert.hasText(key, "Meta key must not be 'null' or blank."); + if (values == Collections.EMPTY_MAP) { + values = new LinkedHashMap<>(2); + } + if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) { this.values.remove(key); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java index 1f54e7049d..ce60798bf5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java @@ -21,6 +21,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -30,6 +31,7 @@ import java.util.concurrent.TimeUnit; import org.bson.Document; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; @@ -52,7 +54,7 @@ public class Query { private static final String RESTRICTED_TYPES_KEY = "_$RESTRICTED_TYPES"; - private final Set> restrictedTypes = new HashSet<>(); + private Set> restrictedTypes = Collections.emptySet(); private final Map criteria = new LinkedHashMap<>(); private @Nullable Field fieldSpec = null; private Sort sort = Sort.unsorted(); @@ -235,8 +237,15 @@ public Query restrict(Class type, Class... additionalTypes) { Assert.notNull(type, "Type must not be null!"); Assert.notNull(additionalTypes, "AdditionalTypes must not be null"); + if (restrictedTypes == Collections.EMPTY_SET) { + restrictedTypes = new HashSet<>(1 + additionalTypes.length); + } + restrictedTypes.add(type); - restrictedTypes.addAll(Arrays.asList(additionalTypes)); + + if (additionalTypes.length > 0) { + restrictedTypes.addAll(Arrays.asList(additionalTypes)); + } return this; } @@ -246,6 +255,17 @@ public Query restrict(Class type, Class... additionalTypes) { */ public Document getQueryObject() { + if (criteria.isEmpty() && restrictedTypes.isEmpty()) { + return BsonUtils.EMPTY_DOCUMENT; + } + + if (criteria.size() == 1 && restrictedTypes.isEmpty()) { + + for (CriteriaDefinition definition : criteria.values()) { + return definition.getCriteriaObject(); + } + } + Document document = new Document(); for (CriteriaDefinition definition : criteria.values()) { @@ -263,7 +283,7 @@ public Document getQueryObject() { * @return the field {@link Document}. */ public Document getFieldsObject() { - return this.fieldSpec == null ? new Document() : fieldSpec.getFieldsObject(); + return this.fieldSpec == null ? BsonUtils.EMPTY_DOCUMENT : fieldSpec.getFieldsObject(); } /** @@ -272,13 +292,12 @@ public Document getFieldsObject() { public Document getSortObject() { if (this.sort.isUnsorted()) { - return new Document(); + return BsonUtils.EMPTY_DOCUMENT; } Document document = new Document(); - this.sort.stream()// - .forEach(order -> document.put(order.getProperty(), order.isAscending() ? 1 : -1)); + this.sort.forEach(order -> document.put(order.getProperty(), order.isAscending() ? 1 : -1)); return document; } @@ -557,7 +576,7 @@ public boolean isSorted() { target.limit = source.getLimit(); target.hint = source.getHint(); target.collation = source.getCollation(); - target.restrictedTypes.addAll(source.getRestrictedTypes()); + target.restrictedTypes = new HashSet<>(source.getRestrictedTypes()); if (source.getMeta().hasValues()) { target.setMeta(new Meta(source.getMeta())); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java index 9a72b3ffc0..84a5b9d47e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java @@ -18,6 +18,8 @@ import java.util.Locale; import org.bson.Document; + +import org.springframework.data.mongodb.util.BsonUtils; import org.springframework.lang.Nullable; /** @@ -157,7 +159,7 @@ public Document getFieldsObject() { return super.getFieldsObject(); } - Document fields = super.getFieldsObject(); + Document fields = BsonUtils.asMutableDocument(super.getFieldsObject()); fields.put(getScoreFieldName(), META_TEXT_SCORE); return fields; @@ -170,15 +172,14 @@ public Document getFieldsObject() { @Override public Document getSortObject() { - Document sort = new Document(); - if (this.sortByScore) { + Document sort = new Document(); sort.put(getScoreFieldName(), META_TEXT_SCORE); + sort.putAll(super.getSortObject()); + return sort; } - sort.putAll(super.getSortObject()); - - return sort; + return super.getSortObject(); } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java index 34cab18c31..bdea768d31 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java @@ -56,10 +56,10 @@ public enum Position { } private boolean isolated = false; - private Set keysToUpdate = new HashSet<>(); - private Map modifierOps = new LinkedHashMap<>(); - private Map pushCommandBuilders = new LinkedHashMap<>(1); - private List arrayFilters = new ArrayList<>(); + private final Set keysToUpdate = new HashSet<>(); + private final Map modifierOps = new LinkedHashMap<>(); + private Map pushCommandBuilders = Collections.emptyMap(); + private List arrayFilters = Collections.emptyList(); /** * Static factory method to create an Update using the provided key @@ -193,6 +193,11 @@ public Update push(String key, @Nullable Object value) { public PushOperatorBuilder push(String key) { if (!pushCommandBuilders.containsKey(key)) { + + if (pushCommandBuilders == Collections.EMPTY_MAP) { + pushCommandBuilders = new LinkedHashMap<>(1); + } + pushCommandBuilders.put(key, new PushOperatorBuilder(key)); } return pushCommandBuilders.get(key); @@ -412,6 +417,10 @@ public Update isolated() { */ public Update filterArray(CriteriaDefinition criteria) { + if (arrayFilters == Collections.EMPTY_LIST) { + this.arrayFilters = new ArrayList<>(); + } + this.arrayFilters.add(criteria::getCriteriaObject); return this; } @@ -427,6 +436,10 @@ public Update filterArray(CriteriaDefinition criteria) { */ public Update filterArray(String identifier, Object expression) { + if (arrayFilters == Collections.EMPTY_LIST) { + this.arrayFilters = new ArrayList<>(); + } + this.arrayFilters.add(() -> new Document(identifier, expression)); return this; } @@ -455,6 +468,15 @@ public List getArrayFilters() { return Collections.unmodifiableList(this.arrayFilters); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.UpdateDefinition#hasArrayFilters() + */ + @Override + public boolean hasArrayFilters() { + return !this.arrayFilters.isEmpty(); + } + /** * This method is not called anymore rather override {@link #addMultiFieldOperation(String, String, Object)}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java index d452ad662f..c540a14603 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java @@ -60,12 +60,26 @@ */ public class BsonUtils { + /** + * The empty document (immutable). This document is serializable. + * + * @since 3.2.5 + */ + public static final Document EMPTY_DOCUMENT = new EmptyDocument(); + @SuppressWarnings("unchecked") @Nullable public static T get(Bson bson, String key) { return (T) asMap(bson).get(key); } + /** + * Return the {@link Bson} object as {@link Map}. Depending on the input type, the return value can be either a casted + * version of {@code bson} or a converted (detached from the original value). + * + * @param bson + * @return + */ public static Map asMap(Bson bson) { if (bson instanceof Document) { @@ -81,6 +95,55 @@ public static Map asMap(Bson bson) { return (Map) bson.toBsonDocument(Document.class, MongoClientSettings.getDefaultCodecRegistry()); } + /** + * Return the {@link Bson} object as {@link Document}. Depending on the input type, the return value can be either a + * casted version of {@code bson} or a converted (detached from the original value). + * + * @param bson + * @return + * @since 3.2.5 + */ + public static Document asDocument(Bson bson) { + + if (bson instanceof Document) { + return (Document) bson; + } + + Map map = asMap(bson); + + if (map instanceof Document) { + return (Document) map; + } + + return new Document(map); + } + + /** + * Return the {@link Bson} object as mutable {@link Document} containing all entries from {@link Bson}. + * + * @param bson + * @return a mutable {@link Document} containing all entries from {@link Bson}. + * @since 3.2.5 + */ + public static Document asMutableDocument(Bson bson) { + + if (bson instanceof EmptyDocument) { + bson = new Document(asDocument(bson)); + } + + if (bson instanceof Document) { + return (Document) bson; + } + + Map map = asMap(bson); + + if (map instanceof Document) { + return (Document) map; + } + + return new Document(map); + } + public static void addToMap(Bson bson, String key, @Nullable Object value) { if (bson instanceof Document) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/EmptyDocument.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/EmptyDocument.java new file mode 100644 index 0000000000..83c95c82e5 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/EmptyDocument.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.util; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; + +import org.bson.Document; +import org.jetbrains.annotations.Nullable; + +/** + * Empty variant of {@link Document}. + * + * @author Mark Paluch + */ +class EmptyDocument extends Document { + + @Override + public Document append(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public void replaceAll(BiFunction function) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean replace(String key, Object oldValue, Object newValue) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public Object replace(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + return Collections.emptySet(); + } + + @Override + public Collection values() { + return Collections.emptyList(); + } + + @Override + public Set keySet() { + return Collections.emptySet(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/MongoDatabaseUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/MongoDatabaseUtilsUnitTests.java index 8cb222f0e6..5b0cd81cc2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/MongoDatabaseUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/MongoDatabaseUtilsUnitTests.java @@ -109,6 +109,30 @@ void shouldNotStartSessionWhenNoTransactionOngoing() { verify(dbFactory, never()).withSession(any(ClientSession.class)); } + @Test // GH-3760 + void shouldJustReturnDatabaseIfSessionSynchronizationDisabled() throws Exception { + + when(dbFactory.getMongoDatabase()).thenReturn(db); + + JtaTransactionManager txManager = new JtaTransactionManager(userTransaction); + TransactionTemplate txTemplate = new TransactionTemplate(txManager); + + txTemplate.execute(new TransactionCallbackWithoutResult() { + + @Override + protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { + + MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.NEVER); + + assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse(); + } + }); + + verify(userTransaction).getStatus(); + verifyNoMoreInteractions(userTransaction); + verifyNoInteractions(session); + } + @Test // DATAMONGO-1920 void shouldParticipateInOngoingJtaTransactionWithCommitWhenSessionSychronizationIsAny() throws Exception { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtilsUnitTests.java index 60a7ff9a47..a7393a1392 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtilsUnitTests.java @@ -88,6 +88,20 @@ void isTransactionActiveShouldLookupTxForActiveTransactionSynchronizationViaTxMa }).as(StepVerifier::create).expectNext(true).verifyComplete(); } + @Test // GH-3760 + void shouldJustReturnDatabaseIfSessionSynchronizationDisabled() { + + when(databaseFactory.getMongoDatabase()).thenReturn(Mono.just(db)); + + ReactiveMongoDatabaseUtils.getDatabase(databaseFactory, SessionSynchronization.NEVER) // + .as(StepVerifier::create) // + .expectNextCount(1) // + .verifyComplete(); + + verify(databaseFactory, never()).getSession(any()); + verify(databaseFactory, never()).withSession(any(ClientSession.class)); + } + @Test // DATAMONGO-2265 void shouldNotStartSessionWhenNoTransactionOngoing() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index 147d2e49c3..b1d3d6a839 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -101,6 +101,7 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.core.timeseries.Granularity; +import org.springframework.data.mongodb.util.BsonUtils; import org.springframework.lang.Nullable; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.CollectionUtils; @@ -1071,7 +1072,7 @@ void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() { template.doFind("star-wars", new Document(), new Document(), Person.class, PersonSpELProjection.class, CursorPreparer.NO_OP_PREPARER); - verify(findIterable).projection(eq(new Document())); + verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT)); } @Test // DATAMONGO-1733, DATAMONGO-2041 @@ -1098,7 +1099,7 @@ void doesNotApplyFieldsWhenTargetIsNotAProjection() { template.doFind("star-wars", new Document(), new Document(), Person.class, Person.class, CursorPreparer.NO_OP_PREPARER); - verify(findIterable).projection(eq(new Document())); + verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT)); } @Test // DATAMONGO-1733 @@ -1107,7 +1108,7 @@ void doesNotApplyFieldsWhenTargetExtendsDomainType() { template.doFind("star-wars", new Document(), new Document(), Person.class, PersonExtended.class, CursorPreparer.NO_OP_PREPARER); - verify(findIterable).projection(eq(new Document())); + verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT)); } @Test // DATAMONGO-1348, DATAMONGO-2264 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java index 01dddcd084..69da412073 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java @@ -237,11 +237,8 @@ void clonedQueryShouldNotDependOnCriteriaFromSource() { source.addCriteria(where("From one make ten").is("and two let be.")); Query target = Query.of(source); - compareQueries(target, source); - source.addCriteria(where("Make even three").is("then rich you'll be.")); - - assertThat(target.getQueryObject()).isEqualTo(new Document("From one make ten", "and two let be.")) - .isNotEqualTo(source.getQueryObject()); + assertThat(target.getQueryObject()).containsAllEntriesOf(new Document("From one make ten", "and two let be.")) + .isNotSameAs(source.getQueryObject()); } @Test // DATAMONGO-1783 @@ -353,9 +350,12 @@ void queryOfShouldWorkOnProxiedObjects() { private void compareQueries(Query actual, Query expected) { assertThat(actual.getCollation()).isEqualTo(expected.getCollation()); - assertThat(actual.getSortObject()).isEqualTo(expected.getSortObject()); - assertThat(actual.getFieldsObject()).isEqualTo(expected.getFieldsObject()); - assertThat(actual.getQueryObject()).isEqualTo(expected.getQueryObject()); + assertThat(actual.getSortObject()).hasSameSizeAs(expected.getSortObject()) + .containsAllEntriesOf(expected.getSortObject()); + assertThat(actual.getFieldsObject()).hasSameSizeAs(expected.getFieldsObject()) + .containsAllEntriesOf(expected.getFieldsObject()); + assertThat(actual.getQueryObject()).hasSameSizeAs(expected.getQueryObject()) + .containsAllEntriesOf(expected.getQueryObject()); assertThat(actual.getHint()).isEqualTo(expected.getHint()); assertThat(actual.getLimit()).isEqualTo(expected.getLimit()); assertThat(actual.getSkip()).isEqualTo(expected.getSkip()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java index c6c1b140cd..9d8400995a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java @@ -31,11 +31,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.convert.DbRefResolver; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; @@ -128,7 +125,7 @@ void propagatesRootExceptionForInvalidQuery() { @Test // DATAMONGO-1345, DATAMONGO-1735 void doesNotDeriveFieldSpecForNormalDomainType() { - assertThat(deriveQueryFromMethod("findPersonBy", new Object[0]).getFieldsObject()).isEqualTo(new Document()); + assertThat(deriveQueryFromMethod("findPersonBy", new Object[0]).getFieldsObject()).isEmpty(); } @Test // DATAMONGO-1345 @@ -173,7 +170,7 @@ void doesNotCreateFieldsObjectForOpenProjection() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findAllBy"); - assertThat(query.getFieldsObject()).isEqualTo(new Document()); + assertThat(query.getFieldsObject()).isEmpty(); } @Test // DATAMONGO-1865