Compare commits

..

23 Commits

Author SHA1 Message Date
Mark Paluch
e5e8fa45c2 DATAMONGO-1776 - Release version 2.0 GA (Kay). 2017-10-02 11:10:22 +02:00
Mark Paluch
f5ad4e42f9 DATAMONGO-1776 - Prepare 2.0 GA (Kay). 2017-10-02 11:09:17 +02:00
Mark Paluch
e6b7d2ffd0 DATAMONGO-1776 - Updated changelog. 2017-10-02 11:09:09 +02:00
Mark Paluch
5b24d3fd0b DATAMONGO-1778 - Polishing.
Javadoc, modifiers.

Original pull request: #503.
2017-10-02 10:38:20 +02:00
Christoph Strobl
10f13c8f37 DATAMONGO-1778 - Polishing.
Migrate UpdateTests to AssertJ and adjust constructor visibility.

Original pull request: #503.
2017-10-02 10:38:20 +02:00
Christoph Strobl
c05f8f056c DATAMONGO-1778 - Fix equals() and hashCode() for Update.
We now include the entire update document with its modifiers and the isolation flag when computing the hash code and comparing for object equality.

Original pull request: #503.
2017-10-02 10:37:34 +02:00
Mark Paluch
dbd38a8e82 DATAMONGO-1791 - Polishing.
Replace RxJava 1 repositories with RxJava 2 repositories. Fix broken links. Fix duplicate section ids.
2017-09-27 12:19:11 +02:00
Mark Paluch
77b1f3cb37 DATAMONGO-1791 - Adapt to changed Spring Framework 5 documentation structure.
Update links in the reference docs to their new locations.
2017-09-27 12:13:51 +02:00
Mark Paluch
5444ac39b5 DATAMONGO-1785 - Downgrade to CDI 1.0.
We now build against CDI 1.0 again while using CDI 2.0 for testing.
2017-09-21 13:50:39 +02:00
Mark Paluch
cf476b9bc8 DATAMONGO-1787 - Added explicit automatic module name for JDK 9. 2017-09-21 13:50:39 +02:00
Christoph Strobl
f28d47b01b DATAMONGO-1777 - Polishing. 2017-09-21 11:32:52 +02:00
Christoph Strobl
5bf03cfa70 DATAMONGO-1777 - Pretty print Modifiers when calling Update.toString().
We now make sure to pretty print Update modifiers by safely rendering to a json representation including an optional $isolated opereator if applicable.
Along the way we also fix a flaw in PushOperationBuilder ignoring eg. $position when pushing single values.
2017-09-21 11:32:50 +02:00
Oliver Gierke
98e893636b DATAMONGO-1779 - Polishing.
Fixed imports.
2017-09-21 11:31:45 +02:00
Oliver Gierke
4b552b051e DATAMONGO-1779 - Fixed handling of empty queries in MongoTemplate.find(…).
Calls to MongoTemplate.find(…) were routed to ….findAll(…) in case no criteria definition or sort was defined on the query. This however neglected that cursor preparation aspects (limits, skips) are defined on the query as well which cause them not to be applied correctly. Removed the over-optimistic re-routing so that normal query execution now always gets applied.
2017-09-21 11:31:44 +02:00
Mark Paluch
1c295b62c6 DATAMONGO-1786 - Adapt tests to nullability validation in Spring Data Commons.
Related issue: DATACMNS-1157.
2017-09-21 11:27:41 +02:00
Christoph Strobl
0a8458a045 DATAMONGO-1784 - Polishing.
Update JavaDoc, enforce nullability constraints and add tests.

Original Pull Request: #501
2017-09-20 12:47:29 +02:00
Sergey Shcherbakov
a3b9fb33ea DATAMONGO-1784 - Add expression support to GroupOperation#sum().
We now allow passing an AggregationExpression to GroupOperation.sum which allows construction of more complex expressions.

Original Pull Request: #501
2017-09-20 12:46:14 +02:00
Christoph Strobl
3d651b72ad DATAMONGO-1782 - Polishing.
toCyclePath now returns an empty String when Path does not cycle.
Also split and add tests and move code to Java8.

Original Pull Request: #500
2017-09-19 11:07:04 +02:00
Mark Paluch
187c25bcc0 DATAMONGO-1782 - Detect type cycles using PersistentProperty paths.
We now rely on PersistentProperty paths to detect cycles between types. Cycles are detected when building up the path object and traversing PersistentProperty stops after the cycle was hit for the second time to generated indexes for at least one hierarchy level.

Previously, we used String-based property dot paths and compared whether paths to a particular property was already found by a substring search which caused false positives if a property was reachable via multiple paths.

Original Pull Request: #500
2017-09-19 10:03:40 +02:00
Mark Paluch
087482d82e DATAMONGO-1785 - Upgrade to OpenWebBeans 2.0.1
Upgrade also to Equalsverifier 1.7.8 to resolve ASM version conflict.
2017-09-18 15:21:57 +02:00
Mark Paluch
e80d1df571 DATAMONGO-1781 - Update what's new in reference documentation. 2017-09-14 14:09:08 +02:00
Oliver Gierke
a9b1b640c0 DATAMONGO-1754 - After release cleanups. 2017-09-11 17:40:21 +02:00
Oliver Gierke
b888864407 DATAMONGO-1754 - Prepare next development iteration. 2017-09-11 17:40:18 +02:00
34 changed files with 796 additions and 416 deletions

10
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.RC3</version>
<version>2.0.0.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.0.0.RC3</version>
<version>2.0.0.RELEASE</version>
</parent>
<modules>
@@ -27,7 +27,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.0.0.RC3</springdata.commons>
<springdata.commons>2.0.0.RELEASE</springdata.commons>
<mongo>3.5.0</mongo>
<mongo.reactivestreams>1.6.0</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
@@ -183,8 +183,8 @@
<repositories>
<repository>
<id>spring-libs-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
<id>spring-libs-release</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.RC3</version>
<version>2.0.0.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.RC3</version>
<version>2.0.0.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -16,6 +16,7 @@
<properties>
<jpa>2.1.1</jpa>
<hibernate>5.2.1.Final</hibernate>
<java-module-name>spring.data.mongodb.cross.store</java-module-name>
</properties>
<dependencies>
@@ -48,7 +49,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.0.0.RC3</version>
<version>2.0.0.RELEASE</version>
</dependency>
<!-- reactive -->

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.RC3</version>
<version>2.0.0.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -11,13 +11,14 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.0.RC3</version>
<version>2.0.0.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<objenesis>1.3</objenesis>
<equalsverifier>1.5</equalsverifier>
<equalsverifier>1.7.8</equalsverifier>
<java-module-name>spring.data.mongodb</java-module-name>
</properties>
<dependencies>
@@ -137,6 +138,21 @@
</dependency>
<!-- CDI -->
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jcdi_2.0_spec</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
@@ -146,26 +162,19 @@
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>${cdi}</version>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax-annotation-api}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans.test</groupId>
<artifactId>cditest-owb</artifactId>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>test</scope>
</dependency>
<!-- JSR 303 Validation -->
<dependency>
<groupId>javax.validation</groupId>

View File

@@ -59,6 +59,7 @@ import com.mongodb.client.result.UpdateResult;
* @author Christoph Strobl
* @author Thomas Darimont
* @author Maninder Singh
* @author Mark Paluch
*/
public interface MongoOperations extends FluentMongoOperations {
@@ -850,7 +851,7 @@ public interface MongoOperations extends FluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert" >
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" >
* Spring's Type Conversion"</a> for more details.
* <p/>
* <p/>
@@ -907,7 +908,7 @@ public interface MongoOperations extends FluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert" >
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" >
* Spring's Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
@@ -924,7 +925,7 @@ public interface MongoOperations extends FluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
* http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert">Spring's
* http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's
* Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.

View File

@@ -24,8 +24,19 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bson.Document;
@@ -761,16 +772,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @see org.springframework.data.mongodb.core.MongoOperations#findOne(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
*/
@Override
public <T> List<T> find(final Query query, Class<T> entityClass, String collectionName) {
public <T> List<T> find(Query query, Class<T> entityClass, String collectionName) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
Assert.notNull(entityClass, "EntityClass must not be null!");
if (query.getQueryObject().isEmpty() && query.getSortObject().isEmpty()) {
return findAll(entityClass, collectionName);
}
return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
new QueryCursorPreparer(query, entityClass));
}

View File

@@ -613,7 +613,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert" >
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" >
* Spring's Type Conversion"</a> for more details.
* <p/>
* <p/>
@@ -673,7 +673,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert" >
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" >
* Spring's Type Conversion"</a> for more details.
* <p/>
* <p/>
@@ -721,7 +721,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert" >
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" >
* Spring's Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
@@ -739,7 +739,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
* http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert">Spring's
* http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's
* Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
@@ -758,7 +758,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert" >
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" >
* Spring's Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
@@ -776,7 +776,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
* http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert">Spring's
* http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's
* Type Conversion"</a> for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016. the original author or authors.
* Copyright 2016-2017 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.
@@ -20,7 +20,7 @@ import org.springframework.util.Assert;
/**
* An {@link AggregationExpression} that renders a MongoDB Aggregation Framework expression from the AST of a
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html">SpEL
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions">SpEL
* expression</a>. <br />
* <br />
* <strong>Samples:</strong> <br />
@@ -35,6 +35,7 @@ import org.springframework.util.Assert;
* </code>
*
* @author Christoph Strobl
* @author Mark Paluch
* @see SpelExpressionTransformer
* @since 1.10
*/

View File

@@ -38,6 +38,7 @@ import org.springframework.util.Assert;
* @author Gustavo de Geus
* @author Christoph Strobl
* @author Mark Paluch
* @author Sergey Shcherbakov
* @since 1.3
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/group/">MongoDB Aggregation Framework: $group</a>
*/
@@ -155,6 +156,21 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
return sum(reference, null);
}
/**
* Generates an {@link GroupOperationBuilder} for an {@code $sum}-expression for the given
* {@link AggregationExpression}.
*
* @param expr must not be {@literal null}.
* @return new instance of {@link GroupOperationBuilder}. Never {@literal null}.
* @throws IllegalArgumentException when {@code expr} is {@literal null}.
* @since 1.10.8
*/
public GroupOperationBuilder sum(AggregationExpression expr) {
Assert.notNull(expr, "Expr must not be null!");
return newBuilder(GroupOps.SUM, null, expr);
}
private GroupOperationBuilder sum(@Nullable String reference, @Nullable Object value) {
return newBuilder(GroupOps.SUM, reference, value);
}

View File

@@ -15,15 +15,20 @@
*/
package org.springframework.data.mongodb.core.index;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,7 +37,9 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.CycleGuard.Path;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.TextIndexIncludeOptions.IncludeStrategy;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexedFieldSpec;
@@ -98,87 +105,89 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
Document document = root.findAnnotation(Document.class);
Assert.notNull(document, "Given entity is not collection root.");
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
final List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root));
indexInformation.addAll(potentiallyCreateTextIndexDefinition(root));
final CycleGuard guard = new CycleGuard();
root.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
try {
if (persistentProperty.isEntity()) {
indexInformation
.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getRequiredActualType(),
persistentProperty.getFieldName(), root.getCollection(), guard));
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(
persistentProperty.getFieldName(), root.getCollection(), persistentProperty);
if (indexDefinitionHolder != null) {
indexInformation.add(indexDefinitionHolder);
}
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
}
}
});
root.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> this
.potentiallyAddIndexForProperty(root, property, indexInformation, new CycleGuard()));
indexInformation.addAll(resolveIndexesForDbrefs("", root.getCollection(), root));
return indexInformation;
}
private void potentiallyAddIndexForProperty(MongoPersistentEntity<?> root, MongoPersistentProperty persistentProperty,
List<IndexDefinitionHolder> indexes, CycleGuard guard) {
try {
if (persistentProperty.isEntity()) {
indexes.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(),
persistentProperty.getFieldName(), Path.of(persistentProperty), root.getCollection(), guard));
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(
persistentProperty.getFieldName(), root.getCollection(), persistentProperty);
if (indexDefinitionHolder != null) {
indexes.add(indexDefinitionHolder);
}
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
}
}
/**
* Recursively resolve and inspect properties of given {@literal type} for indexes to be created.
*
* @param type
* @param path The {@literal "dot} path.
* @param dotPath The {@literal "dot} path.
* @param path {@link PersistentProperty} path for cycle detection.
* @param collection
* @param guard
* @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property
* types. Will never be {@code null}.
*/
private List<IndexDefinitionHolder> resolveIndexForClass(final TypeInformation<?> type, final String path,
final String collection, final CycleGuard guard) {
private List<IndexDefinitionHolder> resolveIndexForClass(final TypeInformation<?> type, final String dotPath,
final Path path, final String collection, final CycleGuard guard) {
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(type);
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, entity));
final List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(dotPath, collection, entity));
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
entity.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> this
.guradAndPotentiallyAddIndexForProperty(property, dotPath, path, collection, indexInformation, guard));
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + persistentProperty.getFieldName();
guard.protect(persistentProperty, path);
if (persistentProperty.isEntity()) {
try {
indexInformation.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(),
propertyDotPath, collection, guard));
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
}
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(propertyDotPath,
collection, persistentProperty);
if (indexDefinitionHolder != null) {
indexInformation.add(indexDefinitionHolder);
}
}
});
indexInformation.addAll(resolveIndexesForDbrefs(path, collection, entity));
indexInformation.addAll(resolveIndexesForDbrefs(dotPath, collection, entity));
return indexInformation;
}
private void guradAndPotentiallyAddIndexForProperty(MongoPersistentProperty persistentProperty, String dotPath,
Path path, String collection, List<IndexDefinitionHolder> indexes, CycleGuard guard) {
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "") + persistentProperty.getFieldName();
Path propertyPath = path.append(persistentProperty);
guard.protect(persistentProperty, propertyPath);
if (persistentProperty.isEntity()) {
try {
indexes.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(), propertyDotPath,
propertyPath, collection, guard));
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
}
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(propertyDotPath, collection,
persistentProperty);
if (indexDefinitionHolder != null) {
indexes.add(indexDefinitionHolder);
}
}
@Nullable
private IndexDefinitionHolder createIndexDefinitionHolderForProperty(String dotPath, String collection,
MongoPersistentProperty persistentProperty) {
@@ -227,8 +236,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
try {
appendTextIndexInformation("", indexDefinitionBuilder, root, new TextIndexIncludeOptions(IncludeStrategy.DEFAULT),
new CycleGuard());
appendTextIndexInformation("", Path.empty(), indexDefinitionBuilder, root,
new TextIndexIncludeOptions(IncludeStrategy.DEFAULT), new CycleGuard());
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
}
@@ -244,15 +253,16 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
private void appendTextIndexInformation(final String dotPath, final TextIndexDefinitionBuilder indexDefinitionBuilder,
final MongoPersistentEntity<?> entity, final TextIndexIncludeOptions includeOptions, final CycleGuard guard) {
private void appendTextIndexInformation(final String dotPath, final Path path,
final TextIndexDefinitionBuilder indexDefinitionBuilder, final MongoPersistentEntity<?> entity,
final TextIndexIncludeOptions includeOptions, final CycleGuard guard) {
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
guard.protect(persistentProperty, dotPath);
guard.protect(persistentProperty, path);
if (persistentProperty.isExplicitLanguageProperty() && !StringUtils.hasText(dotPath)) {
indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName());
@@ -265,6 +275,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "")
+ persistentProperty.getFieldName();
Path propertyPath = path.append(persistentProperty);
TextIndexedFieldSpec parentFieldSpec = includeOptions.getParentFieldSpec();
Float weight = indexed != null ? indexed.weight()
: (parentFieldSpec != null ? parentFieldSpec.getWeight() : 1.0F);
@@ -278,9 +290,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
try {
appendTextIndexInformation(propertyDotPath, indexDefinitionBuilder,
mappingContext.getRequiredPersistentEntity(persistentProperty.getActualType()), optionsForNestedType,
guard);
appendTextIndexInformation(propertyDotPath, propertyPath, indexDefinitionBuilder,
mappingContext.getPersistentEntity(persistentProperty.getActualType()), optionsForNestedType, guard);
} catch (CyclicPropertyReferenceException e) {
LOGGER.info(e.getMessage());
} catch (InvalidDataAccessApiUsageException e) {
@@ -308,13 +319,13 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
protected List<IndexDefinitionHolder> createCompoundIndexDefinitions(String dotPath, String fallbackCollection,
MongoPersistentEntity<?> entity) {
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<>();
CompoundIndexes indexes = entity.findAnnotation(CompoundIndexes.class);
if (indexes != null) {
for (CompoundIndex index : indexes.value()) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
}
indexDefinitions = Arrays.stream(indexes.value())
.map(index -> createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity))
.collect(Collectors.toList());
}
CompoundIndex index = entity.findAnnotation(CompoundIndex.class);
@@ -384,6 +395,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
* @param persitentProperty
* @return
*/
@Nullable
protected IndexDefinitionHolder createIndexDefinition(String dotPath, String collection,
MongoPersistentProperty persitentProperty) {
@@ -428,6 +440,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
* @param persistentProperty
* @return
*/
@Nullable
protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath, String collection,
MongoPersistentProperty persistentProperty) {
@@ -471,86 +484,70 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private List<IndexDefinitionHolder> resolveIndexesForDbrefs(final String path, final String collection,
MongoPersistentEntity<?> entity) {
final List<IndexDefinitionHolder> indexes = new ArrayList<IndexDefinitionHolder>(0);
entity.doWithAssociations(new AssociationHandler<MongoPersistentProperty>() {
@Override
public void doWithAssociation(Association<MongoPersistentProperty> association) {
MongoPersistentProperty property = association.getInverse();
String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + property.getFieldName();
if (property.isAnnotationPresent(GeoSpatialIndexed.class) || property.isAnnotationPresent(TextIndexed.class)) {
throw new MappingException(
String.format("Cannot create geospatial-/text- index on DBRef in collection '%s' for path '%s'.",
collection, propertyDotPath));
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(propertyDotPath,
collection, property);
if (indexDefinitionHolder != null) {
indexes.add(indexDefinitionHolder);
}
}
});
final List<IndexDefinitionHolder> indexes = new ArrayList<>(0);
entity.doWithAssociations((AssociationHandler<MongoPersistentProperty>) association -> this
.resolveAndAddIndexesForAssociation(association, indexes, path, collection));
return indexes;
}
private void resolveAndAddIndexesForAssociation(Association<MongoPersistentProperty> association,
List<IndexDefinitionHolder> indexes, String path, String collection) {
MongoPersistentProperty property = association.getInverse();
String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + property.getFieldName();
if (property.isAnnotationPresent(GeoSpatialIndexed.class) || property.isAnnotationPresent(TextIndexed.class)) {
throw new MappingException(
String.format("Cannot create geospatial-/text- index on DBRef in collection '%s' for path '%s'.", collection,
propertyDotPath));
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(propertyDotPath, collection,
property);
if (indexDefinitionHolder != null) {
indexes.add(indexDefinitionHolder);
}
}
/**
* {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
* to detect potential cycles within the references.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
static class CycleGuard {
private final Map<String, List<Path>> propertyTypeMap;
CycleGuard() {
this.propertyTypeMap = new LinkedHashMap<String, List<Path>>();
}
private final Set<String> seenProperties = new HashSet<>();
/**
* Detect a cycle in a property path if the property was seen at least once.
*
* @param property The property to inspect
* @param path The path under which the property can be reached.
* @param path The type path under which the property can be reached.
* @throws CyclicPropertyReferenceException in case a potential cycle is detected.
* @see Path#cycles(MongoPersistentProperty, String)
* @see Path#isCycle()
*/
void protect(MongoPersistentProperty property, String path) throws CyclicPropertyReferenceException {
void protect(MongoPersistentProperty property, Path path) throws CyclicPropertyReferenceException {
String propertyTypeKey = createMapKey(property);
if (propertyTypeMap.containsKey(propertyTypeKey)) {
if (!seenProperties.add(propertyTypeKey)) {
List<Path> paths = propertyTypeMap.get(propertyTypeKey);
for (Path existingPath : paths) {
if (existingPath.cycles(property, path) && property.isEntity()) {
paths.add(new Path(property, path));
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
existingPath.getPath());
}
if (path.isCycle()) {
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
path.toCyclePath());
}
paths.add(new Path(property, path));
} else {
ArrayList<Path> paths = new ArrayList<Path>();
paths.add(new Path(property, path));
propertyTypeMap.put(propertyTypeKey, paths);
}
}
private String createMapKey(MongoPersistentProperty property) {
return property.getOwner().getType().getSimpleName() + ":" + property.getFieldName();
return ClassUtils.getShortName(property.getOwner().getType()) + ":" + property.getFieldName();
}
/**
* Path defines the property and its full path from the document root. <br />
* Path defines the full property path from the document root. <br />
* A {@link Path} with {@literal spring.data.mongodb} would be created for the property {@code Three.mongodb}.
*
* <pre>
@@ -571,39 +568,117 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
* </pre>
*
* @author Christoph Strobl
* @author Mark Paluch
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
static class Path {
private final MongoPersistentProperty property;
private final String path;
private static final Path EMPTY = new Path(Collections.emptyList(), false);
Path(MongoPersistentProperty property, String path) {
private final List<PersistentProperty<?>> elements;
private final boolean cycle;
this.property = property;
this.path = path;
}
public String getPath() {
return path;
/**
* @return an empty {@link Path}.
* @since 1.10.8
*/
static Path empty() {
return EMPTY;
}
/**
* Checks whether the given property is owned by the same entity and if it has been already visited by a subset of
* the current path. Given {@literal foo.bar.bar} cycles if {@literal foo.bar} has already been visited and
* {@code class Bar} contains a property of type {@code Bar}. The previously mentioned path would not cycle if
* {@code class Bar} contained a property of type {@code SomeEntity} named {@literal bar}.
* Creates a new {@link Path} from the initial {@link PersistentProperty}.
*
* @param property
* @param path
* @return
* @param initial must not be {@literal null}.
* @return the new {@link Path}.
* @since 1.10.8
*/
boolean cycles(MongoPersistentProperty property, String path) {
static Path of(PersistentProperty<?> initial) {
return new Path(Collections.singletonList(initial), false);
}
if (!property.getOwner().equals(this.property.getOwner())) {
return false;
/**
* Creates a new {@link Path} by appending a {@link PersistentProperty breadcrumb} to the path.
*
* @param breadcrumb must not be {@literal null}.
* @return the new {@link Path}.
* @since 1.10.8
*/
Path append(PersistentProperty<?> breadcrumb) {
List<PersistentProperty<?>> elements = new ArrayList<>(this.elements.size() + 1);
elements.addAll(this.elements);
elements.add(breadcrumb);
return new Path(elements, this.elements.contains(breadcrumb));
}
/**
* @return {@literal true} if a cycle was detected.
* @since 1.10.8
*/
public boolean isCycle() {
return cycle;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.elements.isEmpty() ? "(empty)" : toPath(this.elements.iterator());
}
/**
* Returns the cycle path truncated to the first discovered cycle. The result for the path
* {@literal foo.bar.baz.bar} is {@literal bar -> baz -> bar}.
*
* @return the cycle path truncated to the first discovered cycle.
* @since 1.10.8
*/
String toCyclePath() {
if (!cycle) {
return "";
}
return path.equals(this.path) || path.contains(this.path + ".") || path.contains("." + this.path);
for (int i = 0; i < this.elements.size(); i++) {
int index = indexOf(this.elements, this.elements.get(i), i + 1);
if (index != -1) {
return toPath(this.elements.subList(i, index + 1).iterator());
}
}
return toString();
}
private static <T> int indexOf(List<T> haystack, T needle, int offset) {
for (int i = offset; i < haystack.size(); i++) {
if (haystack.get(i).equals(needle)) {
return i;
}
}
return -1;
}
private static String toPath(Iterator<PersistentProperty<?>> iterator) {
StringBuilder builder = new StringBuilder();
while (iterator.hasNext()) {
builder.append(iterator.next().getName());
if (iterator.hasNext()) {
builder.append(" -> ");
}
}
return builder.toString();
}
}
}

View File

@@ -17,10 +17,9 @@ package org.springframework.data.mongodb.core.query;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
@@ -70,7 +69,7 @@ public abstract class SerializationUtils {
return Collections.emptyMap();
}
Map<String, Object> result = new HashMap<String, Object>();
Map<String, Object> result = new LinkedHashMap<>();
toFlatMap("", source, result);
return result;
}
@@ -80,12 +79,12 @@ public abstract class SerializationUtils {
if (source instanceof Document) {
Document document = (Document) source;
Iterator<Map.Entry<String, Object>> iter = document.entrySet().iterator();
String pathPrefix = currentPath.isEmpty() ? "" : currentPath + ".";
Iterator<Map.Entry<String, Object>> it = document.entrySet().iterator();
String pathPrefix = currentPath.isEmpty() ? "" : currentPath + '.';
while (iter.hasNext()) {
while (it.hasNext()) {
Map.Entry<String, Object> entry = iter.next();
Map.Entry<String, Object> entry = it.next();
if (entry.getKey().startsWith("$")) {
if (map.containsKey(currentPath)) {
@@ -109,7 +108,7 @@ public abstract class SerializationUtils {
* printing raw {@link Document}s containing complex values before actually converting them into Mongo native types.
*
* @param value
* @return
* @return the serialized value or {@literal null}.
*/
@Nullable
public static String serializeToJsonSafely(@Nullable Object value) {
@@ -119,32 +118,26 @@ public abstract class SerializationUtils {
}
try {
return JSON.serialize(value);
return value instanceof Document ? ((Document) value).toJson() : JSON.serialize(value);
} catch (Exception e) {
if (value instanceof Collection) {
return toString((Collection<?>) value);
} else if (value instanceof Map) {
return toString((Map<?, ?>) value);
} else {
return String.format("{ $java : %s }", value.toString());
return String.format("{ \"$java\" : %s }", value.toString());
}
}
}
private static String toString(Map<?, ?> source) {
return iterableToDelimitedString(source.entrySet(), "{ ", " }", new Converter<Entry<?, ?>, Object>() {
public Object convert(Entry<?, ?> source) {
return String.format("\"%s\" : %s", source.getKey(), serializeToJsonSafely(source.getValue()));
}
});
return iterableToDelimitedString(source.entrySet(), "{ ", " }",
entry -> String.format("\"%s\" : %s", entry.getKey(), serializeToJsonSafely(entry.getValue())));
}
private static String toString(Collection<?> source) {
return iterableToDelimitedString(source, "[ ", " ]", new Converter<Object, Object>() {
public Object convert(Object source) {
return serializeToJsonSafely(source);
}
});
return iterableToDelimitedString(source, "[ ", " ]", SerializationUtils::serializeToJsonSafely);
}
/**
@@ -165,6 +158,7 @@ public abstract class SerializationUtils {
Iterator<T> iterator = source.iterator();
while (iterator.hasNext()) {
builder.append(transformer.convert(iterator.next()));
if (iterator.hasNext()) {
builder.append(", ");

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.core.query;
import static org.springframework.util.ObjectUtils.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -24,6 +22,7 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.bson.Document;
@@ -33,6 +32,7 @@ import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -55,9 +55,9 @@ public class Update {
}
private boolean isolated = false;
private Set<String> keysToUpdate = new HashSet<String>();
private Map<String, Object> modifierOps = new LinkedHashMap<String, Object>();
private Map<String, PushOperatorBuilder> pushCommandBuilders = new LinkedHashMap<String, PushOperatorBuilder>(1);
private Set<String> keysToUpdate = new HashSet<>();
private Map<String, Object> modifierOps = new LinkedHashMap<>();
private Map<String, PushOperatorBuilder> pushCommandBuilders = new LinkedHashMap<>(1);
/**
* Static factory method to create an Update using the provided key
@@ -122,7 +122,8 @@ public class Update {
* @param key
* @param value
* @return
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/setOnInsert/">MongoDB Update operator: $setOnInsert</a>
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/setOnInsert/">MongoDB Update operator:
* $setOnInsert</a>
*/
public Update setOnInsert(String key, Object value) {
addMultiFieldOperation("$setOnInsert", key, value);
@@ -193,7 +194,8 @@ public class Update {
* @param key
* @param values
* @return
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/pushAll/">MongoDB Update operator: $pushAll</a>
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/pushAll/">MongoDB Update operator:
* $pushAll</a>
*/
public Update pushAll(String key, Object[] values) {
addMultiFieldOperation("$pushAll", key, Arrays.asList(values));
@@ -218,7 +220,8 @@ public class Update {
* @param key
* @param value
* @return
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/addToSet/">MongoDB Update operator: $addToSet</a>
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/addToSet/">MongoDB Update operator:
* $addToSet</a>
*/
public Update addToSet(String key, Object value) {
addMultiFieldOperation("$addToSet", key, value);
@@ -257,7 +260,8 @@ public class Update {
* @param key
* @param values
* @return
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/pullAll/">MongoDB Update operator: $pullAll</a>
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/pullAll/">MongoDB Update operator:
* $pullAll</a>
*/
public Update pullAll(String key, Object[] values) {
addMultiFieldOperation("$pullAll", key, Arrays.asList(values));
@@ -270,7 +274,8 @@ public class Update {
* @param oldName
* @param newName
* @return
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/rename/">MongoDB Update operator: $rename</a>
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/rename/">MongoDB Update operator:
* $rename</a>
*/
public Update rename(String oldName, String newName) {
addMultiFieldOperation("$rename", oldName, newName);
@@ -283,7 +288,8 @@ public class Update {
* @param key
* @return
* @since 1.6
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/currentDate/">MongoDB Update operator: $currentDate</a>
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/currentDate/">MongoDB Update operator:
* $currentDate</a>
*/
public Update currentDate(String key) {
@@ -297,7 +303,8 @@ public class Update {
* @param key
* @return
* @since 1.6
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/currentDate/">MongoDB Update operator: $currentDate</a>
* @see <a href="https://docs.mongodb.org/manual/reference/operator/update/currentDate/">MongoDB Update operator:
* $currentDate</a>
*/
public Update currentTimestamp(String key) {
@@ -457,7 +464,7 @@ public class Update {
*/
@Override
public int hashCode() {
return getUpdateObject().hashCode();
return Objects.hash(getUpdateObject(), isolated);
}
/*
@@ -476,7 +483,11 @@ public class Update {
}
Update that = (Update) obj;
return this.getUpdateObject().equals(that.getUpdateObject());
if (this.isolated != that.isolated) {
return false;
}
return Objects.equals(this.getUpdateObject(), that.getUpdateObject());
}
/*
@@ -485,7 +496,14 @@ public class Update {
*/
@Override
public String toString() {
return SerializationUtils.serializeToJsonSafely(getUpdateObject());
Document doc = getUpdateObject();
if (isIsolated()) {
doc.append("$isolated", 1);
}
return SerializationUtils.serializeToJsonSafely(doc);
}
/**
@@ -499,7 +517,7 @@ public class Update {
private Map<String, Modifier> modifiers;
public Modifiers() {
this.modifiers = new LinkedHashMap<String, Modifier>(1);
this.modifiers = new LinkedHashMap<>(1);
}
public Collection<Modifier> getModifiers() {
@@ -510,15 +528,25 @@ public class Update {
this.modifiers.put(modifier.getKey(), modifier);
}
/* (non-Javadoc)
/**
* @return true if no modifiers present.
* @since 2.0
*/
public boolean isEmpty() {
return modifiers.isEmpty();
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return nullSafeHashCode(modifiers);
return Objects.hashCode(modifiers);
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
@@ -533,8 +561,12 @@ public class Update {
}
Modifiers that = (Modifiers) obj;
return Objects.equals(this.modifiers, that.modifiers);
}
return this.modifiers.equals(that.modifiers);
@Override
public String toString() {
return SerializationUtils.serializeToJsonSafely(this.modifiers);
}
}
@@ -543,7 +575,7 @@ public class Update {
*
* @author Christoph Strobl
*/
public static interface Modifier {
public interface Modifier {
/**
* @return the command to send eg. {@code $push}
@@ -554,6 +586,64 @@ public class Update {
* @return value to be sent with command
*/
Object getValue();
/**
* @return a safely serialized JSON representation.
* @since 2.0
*/
default String toJsonString() {
return SerializationUtils.serializeToJsonSafely(Collections.singletonMap(getKey(), getValue()));
}
}
/**
* Abstract {@link Modifier} implementation with defaults for {@link Object#equals(Object)}, {@link Object#hashCode()}
* and {@link Object#toString()}.
*
* @author Christoph Strobl
* @since 2.0
*/
private static abstract class AbstractModifier implements Modifier {
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(getKey()) + ObjectUtils.nullSafeHashCode(getValue());
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null || getClass() != that.getClass()) {
return false;
}
if (!Objects.equals(getKey(), ((Modifier) that).getKey())) {
return false;
}
return Objects.deepEquals(getValue(), ((Modifier) that).getValue());
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return toJsonString();
}
}
/**
@@ -562,11 +652,11 @@ public class Update {
* @author Christoph Strobl
* @author Thomas Darimont
*/
private static class Each implements Modifier {
private static class Each extends AbstractModifier {
private Object[] values;
public Each(Object... values) {
Each(Object... values) {
this.values = extractValues(values);
}
@@ -600,33 +690,6 @@ public class Update {
public Object getValue() {
return this.values;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return nullSafeHashCode(values);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null || getClass() != that.getClass()) {
return false;
}
return nullSafeEquals(values, ((Each) that).values);
}
}
/**
@@ -635,11 +698,11 @@ public class Update {
* @author Christoph Strobl
* @since 1.7
*/
private static class PositionModifier implements Modifier {
private static class PositionModifier extends AbstractModifier {
private final int position;
public PositionModifier(int position) {
PositionModifier(int position) {
this.position = position;
}
@@ -660,11 +723,11 @@ public class Update {
* @author Mark Paluch
* @since 1.10
*/
private static class Slice implements Modifier {
private static class Slice extends AbstractModifier {
private int count;
public Slice(int count) {
Slice(int count) {
this.count = count;
}
@@ -694,7 +757,7 @@ public class Update {
* @author Mark Paluch
* @since 1.10
*/
private static class SortModifier implements Modifier {
private static class SortModifier extends AbstractModifier {
private final Object sort;
@@ -703,7 +766,7 @@ public class Update {
*
* @param direction must not be {@literal null}.
*/
public SortModifier(Direction direction) {
SortModifier(Direction direction) {
Assert.notNull(direction, "Direction must not be null!");
this.sort = direction.isAscending() ? 1 : -1;
@@ -714,7 +777,7 @@ public class Update {
*
* @param sort must not be {@literal null}.
*/
public SortModifier(Sort sort) {
SortModifier(Sort sort) {
Assert.notNull(sort, "Sort must not be null!");
@@ -863,11 +926,17 @@ public class Update {
/**
* Propagates {@link #value(Object)} to {@code $push}
*
* @param values
* @param value
* @return never {@literal null}.
*/
public Update value(Object value) {
return Update.this.push(key, value);
if (this.modifiers.isEmpty()) {
return Update.this.push(key, value);
}
this.modifiers.addModifier(new Each(Collections.singletonList(value)));
return Update.this.push(key, this.modifiers);
}
/*
@@ -876,14 +945,7 @@ public class Update {
*/
@Override
public int hashCode() {
int result = 17;
result += 31 * result + getOuterType().hashCode();
result += 31 * result + nullSafeHashCode(key);
result += 31 * result + nullSafeHashCode(modifiers);
return result;
return Objects.hash(getOuterType(), key, modifiers);
}
/*
@@ -903,11 +965,11 @@ public class Update {
PushOperatorBuilder that = (PushOperatorBuilder) obj;
if (!getOuterType().equals(that.getOuterType())) {
if (!Objects.equals(getOuterType(), that.getOuterType())) {
return false;
}
return nullSafeEquals(this.key, that.key) && nullSafeEquals(this.modifiers, that.modifiers);
return Objects.equals(this.key, that.key) && Objects.equals(this.modifiers, that.modifiers);
}
private Update getOuterType() {

View File

@@ -15,8 +15,12 @@
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
@@ -29,7 +33,18 @@ import lombok.NoArgsConstructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
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 org.bson.types.ObjectId;
import org.hamcrest.collection.IsMapContaining;
@@ -3256,6 +3271,17 @@ public class MongoTemplateTests {
assertThat(template.count(new BasicQuery("{}"), template.determineCollectionName(Sample.class)), is(equalTo(1L)));
}
@Test // DATAMONGO-1779
public void appliesQueryLimitToEmptyQuery() {
Sample first = new Sample("1", "Dave Matthews");
Sample second = new Sample("2", "Carter Beauford");
template.insertAll(Arrays.asList(first, second));
assertThat(template.find(new Query().limit(1), Sample.class)).hasSize(1);
}
static class TypeWithNumbers {
@Id String id;

View File

@@ -41,7 +41,7 @@ public class SerializationUtilsUnitTests {
public void writesSimpleDocument() {
Document document = new Document("foo", "bar");
assertThat(serializeToJsonSafely(document), is("{ \"foo\" : \"bar\"}"));
assertThat(serializeToJsonSafely(document), is("{ \"foo\" : \"bar\" }"));
}
@Test
@@ -49,7 +49,7 @@ public class SerializationUtilsUnitTests {
Document document = new Document("foo", new Complex());
assertThat(serializeToJsonSafely(document),
startsWith("{ \"foo\" : { $java : org.springframework.data.mongodb.core.SerializationUtilsUnitTests$Complex"));
startsWith("{ \"foo\" : { \"$java\" : org.springframework.data.mongodb.core.SerializationUtilsUnitTests$Complex"));
}
@Test
@@ -58,7 +58,7 @@ public class SerializationUtilsUnitTests {
Document document = new Document("foo", Arrays.asList("bar", new Complex()));
Matcher<String> expectedOutput = allOf(
startsWith(
"{ \"foo\" : [ \"bar\", { $java : org.springframework.data.mongodb.core.SerializationUtilsUnitTests$Complex"),
"{ \"foo\" : [ \"bar\", { \"$java\" : org.springframework.data.mongodb.core.SerializationUtilsUnitTests$Complex"),
endsWith(" } ] }"));
assertThat(serializeToJsonSafely(document), is(expectedOutput));
}

View File

@@ -84,6 +84,7 @@ import com.mongodb.client.MongoCollection;
* @author Mark Paluch
* @author Nikolay Bogdanov
* @author Maninder Singh
* @author Sergey Shcherbakov
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
@@ -799,6 +800,49 @@ public class AggregationTests {
assertThat(((Number) good.get("score")).longValue(), is(equalTo(9000L)));
}
@Test // DATAMONGO-1784
public void shouldAllowSumUsingConditionalExpressions() {
mongoTemplate.dropCollection(CarPerson.class);
CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000),
new CarDescriptor.Entry("MAKE1", "MODEL2", 2001));
CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014));
CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2015));
mongoTemplate.save(person1);
mongoTemplate.save(person2);
mongoTemplate.save(person3);
TypedAggregation<CarPerson> agg = Aggregation.newAggregation(CarPerson.class,
unwind("descriptors.carDescriptor.entries"), //
project() //
.and(ConditionalOperators //
.when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good")
.otherwise("meh"))
.as("make") //
.and("descriptors.carDescriptor.entries.model").as("model") //
.and("descriptors.carDescriptor.entries.year").as("year"), //
group("make").sum(ConditionalOperators //
.when(Criteria.where("year").gte(2012)) //
.then(1) //
.otherwise(9000)).as("score"),
sort(ASC, "make"));
AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);
assertThat(result.getMappedResults(), hasSize(2));
Document meh = result.getMappedResults().get(0);
assertThat(meh.get("_id"), is(equalTo("meh")));
assertThat(((Number) meh.get("score")).longValue(), is(equalTo(2L)));
Document good = result.getMappedResults().get(1);
assertThat(good.get("_id"), is(equalTo("good")));
assertThat(((Number) good.get("score")).longValue(), is(equalTo(18000L)));
}
/**
* @see <a href=
* "https://docs.mongodb.com/manual/tutorial/aggregation-with-user-preference-data/#return-the-five-most-common-likes">Return

View File

@@ -25,6 +25,7 @@ import java.util.Arrays;
import org.bson.Document;
import org.junit.Test;
import org.springframework.data.mongodb.core.DocumentTestUtils;
import org.springframework.data.mongodb.core.query.Criteria;
/**
* Unit tests for {@link GroupOperation}.
@@ -216,6 +217,29 @@ public class GroupOperationUnitTests {
assertThat(push, is(new Document("$stdDevPop", "$field")));
}
@Test // DATAMONGO-1784
public void shouldRenderSumWithExpressionInGroup() {
GroupOperation groupOperation = Aggregation //
.group("username") //
.sum(ConditionalOperators //
.when(Criteria.where("foo").is("bar")) //
.then(1) //
.otherwise(-1)) //
.as("foobar");
Document groupClause = extractDocumentFromGroupOperation(groupOperation);
Document foobar = DocumentTestUtils.getAsDocument(groupClause, "foobar");
assertThat(foobar.get("$sum"), is(new Document("$cond",
new Document("if", new Document("$eq", Arrays.asList("$foo", "bar"))).append("then", 1).append("else", -1))));
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1784
public void sumWithNullExpressionShouldThrowException() {
Aggregation.group("username").sum((AggregationExpression) null);
}
private Document extractDocumentFromGroupOperation(GroupOperation groupOperation) {
Document document = groupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
Document groupClause = DocumentTestUtils.getAsDocument(document, "$group");

View File

@@ -62,7 +62,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
/**
* Test resolution of {@link Indexed}.
*
*
* @author Christoph Strobl
*/
public static class IndexResolutionTests {
@@ -297,7 +297,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
/**
* Test resolution of {@link GeoSpatialIndexed}.
*
*
* @author Christoph Strobl
*/
public static class GeoSpatialIndexResolutionTests {
@@ -410,7 +410,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
/**
* Test resolution of {@link CompoundIndexes}.
*
*
* @author Christoph Strobl
*/
public static class CompoundIndexResolutionTests {
@@ -898,6 +898,17 @@ public class MongoPersistentEntityIndexResolverUnitTests {
.resolveIndexForEntity(selfCyclingEntity);
}
@Test // DATAMONGO-1782
public void shouldAllowMultiplePathsToDeeplyType() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
NoCycleManyPathsToDeepValueObject.class);
assertIndexPathAndCollection("l3.valueObject.value", "rules", indexDefinitions.get(0));
assertIndexPathAndCollection("l2.l3.valueObject.value", "rules", indexDefinitions.get(1));
assertThat(indexDefinitions, hasSize(2));
}
@Test // DATAMONGO-1025
public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIndexFixedOnCollection() {
@@ -1055,6 +1066,25 @@ public class MongoPersistentEntityIndexResolverUnitTests {
@Indexed String foo;
}
@Document(collection = "rules")
static class NoCycleManyPathsToDeepValueObject {
private NoCycleLevel3 l3;
private NoCycleLevel2 l2;
}
static class NoCycleLevel2 {
private NoCycleLevel3 l3;
}
static class NoCycleLevel3 {
private ValueObject valueObject;
}
static class ValueObject {
@Indexed private String value;
}
@Document
static class SimilarityHolingBean {
@@ -1171,7 +1201,6 @@ public class MongoPersistentEntityIndexResolverUnitTests {
static class EntityWithGenericTypeWrapperAsElement {
List<GenericEntityWrapper<DocumentWithNamedIndex>> listWithGeneircTypeElement;
}
}
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb.core.index;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@@ -31,45 +31,65 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
/**
* Unit tests for {@link Path}.
*
*
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
@RunWith(MockitoJUnitRunner.Silent.class)
public class PathUnitTests {
@Mock MongoPersistentEntity<?> entityMock;
@Test // DATAMONGO-962
public void shouldIdentifyCycleForOwnerOfSameTypeAndMatchingPath() {
MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo");
assertThat(new Path(property, "foo.bar").cycles(property, "foo.bar.bar"), is(true));
@Before
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setUp() {
when(entityMock.getType()).thenReturn((Class) Object.class);
}
@Test // DATAMONGO-962
@SuppressWarnings("rawtypes")
public void shouldAllowMatchingPathForDifferentOwners() {
@Test // DATAMONGO-962, DATAMONGO-1782
public void shouldIdentifyCycle() {
MongoPersistentProperty existing = createPersistentPropertyMock(entityMock, "foo");
MongoPersistentProperty foo = createPersistentPropertyMock(entityMock, "foo");
MongoPersistentProperty bar = createPersistentPropertyMock(entityMock, "bar");
MongoPersistentEntity entityOfDifferentType = Mockito.mock(MongoPersistentEntity.class);
MongoPersistentProperty toBeVerified = createPersistentPropertyMock(entityOfDifferentType, "foo");
Path path = Path.of(foo).append(bar).append(bar);
assertThat(new Path(existing, "foo.bar").cycles(toBeVerified, "foo.bar.bar"), is(false));
assertThat(path.isCycle(), is(true));
assertThat(path.toCyclePath(), is(equalTo("bar -> bar")));
assertThat(path.toString(), is(equalTo("foo -> bar -> bar")));
}
@Test // DATAMONGO-962
public void shouldAllowEqaulPropertiesOnDifferentPaths() {
@Test // DATAMONGO-1782
public void isCycleShouldReturnFalseWhenNoCyclePresent() {
MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo");
assertThat(new Path(property, "foo.bar").cycles(property, "foo2.bar.bar"), is(false));
MongoPersistentProperty foo = createPersistentPropertyMock(entityMock, "foo");
MongoPersistentProperty bar = createPersistentPropertyMock(entityMock, "bar");
Path path = Path.of(foo).append(bar);
assertThat(path.isCycle(), is(false));
assertThat(path.toCyclePath(), is(equalTo("")));
assertThat(path.toString(), is(equalTo("foo -> bar")));
}
@Test // DATAMONGO-1782
public void isCycleShouldReturnFalseCycleForNonEqualProperties() {
MongoPersistentProperty foo = createPersistentPropertyMock(entityMock, "foo");
MongoPersistentProperty bar = createPersistentPropertyMock(entityMock, "bar");
MongoPersistentProperty bar2 = createPersistentPropertyMock(mock(MongoPersistentEntity.class), "bar");
assertThat(Path.of(foo).append(bar).append(bar2).isCycle(), is(false));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private MongoPersistentProperty createPersistentPropertyMock(MongoPersistentEntity owner, String fieldname) {
private static MongoPersistentProperty createPersistentPropertyMock(MongoPersistentEntity owner, String fieldname) {
MongoPersistentProperty property = Mockito.mock(MongoPersistentProperty.class);
when(property.getOwner()).thenReturn(owner);
when(property.getName()).thenReturn(fieldname);
return property;
}
}

View File

@@ -29,7 +29,7 @@ import nl.jqno.equalsverifier.Warning;
/**
* Unit tests for {@link BasicQuery}.
*
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author John Willemin

View File

@@ -210,7 +210,7 @@ public class QueryTests {
Query query = new Query(where("name").is("foo")).restrict(SpecialDoc.class);
assertThat(query.toString(), is(
"Query: { \"name\" : \"foo\", \"_$RESTRICTED_TYPES\" : [ { $java : class org.springframework.data.mongodb.core.SpecialDoc } ] }, Fields: { }, Sort: { }"));
"Query: { \"name\" : \"foo\", \"_$RESTRICTED_TYPES\" : [ { \"$java\" : class org.springframework.data.mongodb.core.SpecialDoc } ] }, Fields: { }, Sort: { }"));
assertThat(query.getRestrictedTypes(), is(notNullValue()));
assertThat(query.getRestrictedTypes().size(), is(1));
assertThat(query.getRestrictedTypes(), hasItems(Arrays.asList(SpecialDoc.class).toArray(new Class<?>[0])));
@@ -221,7 +221,7 @@ public class QueryTests {
exception.expect(InvalidMongoDbApiUsageException.class);
exception.expectMessage("second 'value' criteria");
exception.expectMessage("already contains '{ \"value\" : { $java : VAL_1 } }'");
exception.expectMessage("already contains '{ \"value\" : { \"$java\" : VAL_1 } }'");
Query query = new Query();
query.addCriteria(where("value").is(EnumType.VAL_1));

View File

@@ -15,8 +15,7 @@
*/
package org.springframework.data.mongodb.core.query;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.*;
import java.util.Collections;
import java.util.Date;
@@ -26,6 +25,7 @@ import org.bson.Document;
import org.joda.time.DateTime;
import org.junit.Test;
import org.springframework.data.mongodb.core.DocumentTestUtils;
import org.springframework.data.mongodb.core.query.Update.Position;
/**
* Test cases for {@link Update}.
@@ -43,51 +43,52 @@ public class UpdateTests {
public void testSet() {
Update u = new Update().set("directory", "/Users/Test/Desktop");
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$set\" : { \"directory\" : \"/Users/Test/Desktop\"}}")));
assertThat(u.getUpdateObject())
.isEqualTo(Document.parse("{ \"$set\" : { \"directory\" : \"/Users/Test/Desktop\"}}"));
}
@Test
public void testSetSet() {
Update u = new Update().set("directory", "/Users/Test/Desktop").set("size", 0);
assertThat(u.getUpdateObject(),
is(Document.parse("{ \"$set\" : { \"directory\" : \"/Users/Test/Desktop\" , \"size\" : 0}}")));
assertThat(u.getUpdateObject())
.isEqualTo((Document.parse("{ \"$set\" : { \"directory\" : \"/Users/Test/Desktop\" , \"size\" : 0}}")));
}
@Test
public void testInc() {
Update u = new Update().inc("size", 1);
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$inc\" : { \"size\" : 1}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$inc\" : { \"size\" : 1}}"));
}
@Test
public void testIncInc() {
Update u = new Update().inc("size", 1).inc("count", 1);
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$inc\" : { \"size\" : 1 , \"count\" : 1}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$inc\" : { \"size\" : 1 , \"count\" : 1}}"));
}
@Test
public void testIncAndSet() {
Update u = new Update().inc("size", 1).set("directory", "/Users/Test/Desktop");
assertThat(u.getUpdateObject(),
is(Document.parse("{ \"$inc\" : { \"size\" : 1} , \"$set\" : { \"directory\" : \"/Users/Test/Desktop\"}}")));
assertThat(u.getUpdateObject()).isEqualTo(
Document.parse("{ \"$inc\" : { \"size\" : 1} , \"$set\" : { \"directory\" : \"/Users/Test/Desktop\"}}"));
}
@Test
public void testUnset() {
Update u = new Update().unset("directory");
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$unset\" : { \"directory\" : 1}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$unset\" : { \"directory\" : 1}}"));
}
@Test
public void testPush() {
Update u = new Update().push("authors", Collections.singletonMap("name", "Sven"));
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$push\" : { \"authors\" : { \"name\" : \"Sven\"}}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$push\" : { \"authors\" : { \"name\" : \"Sven\"}}}"));
}
@Test
@@ -97,8 +98,8 @@ public class UpdateTests {
Map<String, String> m2 = Collections.singletonMap("name", "Maria");
Update u = new Update().pushAll("authors", new Object[] { m1, m2 });
assertThat(u.getUpdateObject(),
is(Document.parse("{ \"$pushAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}]}}")));
assertThat(u.getUpdateObject()).isEqualTo(
Document.parse("{ \"$pushAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}]}}"));
}
@Test // DATAMONGO-354
@@ -110,32 +111,33 @@ public class UpdateTests {
Update u = new Update().pushAll("authors", new Object[] { m1, m2 });
u.pushAll("books", new Object[] { "Spring in Action" });
assertThat(u.getUpdateObject(), is(Document.parse(
"{ \"$pushAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}] , \"books\" : [ \"Spring in Action\"]}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse(
"{ \"$pushAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}] , \"books\" : [ \"Spring in Action\"]}}"));
}
@Test
public void testAddToSet() {
Update u = new Update().addToSet("authors", Collections.singletonMap("name", "Sven"));
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$addToSet\" : { \"authors\" : { \"name\" : \"Sven\"}}}")));
assertThat(u.getUpdateObject())
.isEqualTo(Document.parse("{ \"$addToSet\" : { \"authors\" : { \"name\" : \"Sven\"}}}"));
}
@Test
public void testPop() {
Update u = new Update().pop("authors", Update.Position.FIRST);
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$pop\" : { \"authors\" : -1}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$pop\" : { \"authors\" : -1}}"));
u = new Update().pop("authors", Update.Position.LAST);
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$pop\" : { \"authors\" : 1}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$pop\" : { \"authors\" : 1}}"));
}
@Test
public void testPull() {
Update u = new Update().pull("authors", Collections.singletonMap("name", "Sven"));
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$pull\" : { \"authors\" : { \"name\" : \"Sven\"}}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$pull\" : { \"authors\" : { \"name\" : \"Sven\"}}}"));
}
@Test
@@ -145,77 +147,77 @@ public class UpdateTests {
Map<String, String> m2 = Collections.singletonMap("name", "Maria");
Update u = new Update().pullAll("authors", new Object[] { m1, m2 });
assertThat(u.getUpdateObject(),
is(Document.parse("{ \"$pullAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}]}}")));
assertThat(u.getUpdateObject()).isEqualTo(
Document.parse("{ \"$pullAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}]}}"));
}
@Test
public void testRename() {
Update u = new Update().rename("directory", "folder");
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$rename\" : { \"directory\" : \"folder\"}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$rename\" : { \"directory\" : \"folder\"}}"));
}
@Test
public void testBasicUpdateInc() {
Update u = new Update().inc("size", 1);
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$inc\" : { \"size\" : 1}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$inc\" : { \"size\" : 1}}"));
}
@Test
public void testBasicUpdateIncAndSet() {
Update u = new BasicUpdate("{ \"$inc\" : { \"size\" : 1}}").set("directory", "/Users/Test/Desktop");
assertThat(u.getUpdateObject(),
is(Document.parse("{ \"$inc\" : { \"size\" : 1} , \"$set\" : { \"directory\" : \"/Users/Test/Desktop\"}}")));
assertThat(u.getUpdateObject()).isEqualTo(
Document.parse("{ \"$inc\" : { \"size\" : 1} , \"$set\" : { \"directory\" : \"/Users/Test/Desktop\"}}"));
}
@Test // DATAMONGO-630
public void testSetOnInsert() {
Update u = new Update().setOnInsert("size", 1);
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$setOnInsert\" : { \"size\" : 1}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$setOnInsert\" : { \"size\" : 1}}"));
}
@Test // DATAMONGO-630
public void testSetOnInsertSetOnInsert() {
Update u = new Update().setOnInsert("size", 1).setOnInsert("count", 1);
assertThat(u.getUpdateObject(), is(Document.parse("{ \"$setOnInsert\" : { \"size\" : 1 , \"count\" : 1}}")));
assertThat(u.getUpdateObject()).isEqualTo(Document.parse("{ \"$setOnInsert\" : { \"size\" : 1 , \"count\" : 1}}"));
}
@Test // DATAMONGO-852
public void testUpdateAffectsFieldShouldReturnTrueWhenMultiFieldOperationAddedForField() {
Update update = new Update().set("foo", "bar");
assertThat(update.modifies("foo"), is(true));
assertThat(update.modifies("foo")).isTrue();
}
@Test // DATAMONGO-852
public void testUpdateAffectsFieldShouldReturnFalseWhenMultiFieldOperationAddedForField() {
Update update = new Update().set("foo", "bar");
assertThat(update.modifies("oof"), is(false));
assertThat(update.modifies("oof")).isFalse();
}
@Test // DATAMONGO-852
public void testUpdateAffectsFieldShouldReturnTrueWhenSingleFieldOperationAddedForField() {
Update update = new Update().pullAll("foo", new Object[] { "bar" });
assertThat(update.modifies("foo"), is(true));
assertThat(update.modifies("foo")).isTrue();
}
@Test // DATAMONGO-852
public void testUpdateAffectsFieldShouldReturnFalseWhenSingleFieldOperationAddedForField() {
Update update = new Update().pullAll("foo", new Object[] { "bar" });
assertThat(update.modifies("oof"), is(false));
assertThat(update.modifies("oof")).isFalse();
}
@Test // DATAMONGO-852
public void testUpdateAffectsFieldShouldReturnFalseWhenCalledOnEmptyUpdate() {
assertThat(new Update().modifies("foo"), is(false));
assertThat(new Update().modifies("foo")).isFalse();
}
@Test // DATAMONGO-852
@@ -224,7 +226,7 @@ public class UpdateTests {
Update update = new Update().set("foo", "bar");
Update clone = Update.fromDocument(update.getUpdateObject());
assertThat(clone.modifies("foo"), is(true));
assertThat(clone.modifies("foo")).isTrue();
}
@Test // DATAMONGO-852
@@ -233,7 +235,7 @@ public class UpdateTests {
Update update = new Update().set("foo", "bar");
Update clone = Update.fromDocument(update.getUpdateObject());
assertThat(clone.modifies("oof"), is(false));
assertThat(clone.modifies("oof")).isFalse();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-853
@@ -270,10 +272,10 @@ public class UpdateTests {
.pop("authors", Update.Position.FIRST) //
.set("foo", "bar");
assertThat(actualUpdate, is(equalTo(actualUpdate)));
assertThat(actualUpdate.hashCode(), is(equalTo(actualUpdate.hashCode())));
assertThat(actualUpdate, is(equalTo(expectedUpdate)));
assertThat(actualUpdate.hashCode(), is(equalTo(expectedUpdate.hashCode())));
assertThat(actualUpdate).isEqualTo(actualUpdate);
assertThat(actualUpdate.hashCode()).isEqualTo(actualUpdate.hashCode());
assertThat(actualUpdate).isEqualTo(expectedUpdate);
assertThat(actualUpdate.hashCode()).isEqualTo(expectedUpdate.hashCode());
}
@Test // DATAMONGO-953
@@ -295,58 +297,57 @@ public class UpdateTests {
.pop("authors", Update.Position.FIRST) //
.set("foo", "bar");
assertThat(actualUpdate.toString(), is(equalTo(expectedUpdate.toString())));
assertThat(actualUpdate.getUpdateObject(),
is(Document.parse("{ \"$inc\" : { \"size\" : 1} ," //
+ " \"$set\" : { \"nl\" : null , \"directory\" : \"/Users/Test/Desktop\" , \"foo\" : \"bar\"} , " //
+ "\"$push\" : { \"authors\" : { \"name\" : \"Sven\"}} " //
+ ", \"$pop\" : { \"authors\" : -1}}"))); //
assertThat(actualUpdate.toString()).isEqualTo(expectedUpdate.toString());
assertThat(actualUpdate.getUpdateObject()).isEqualTo(Document.parse("{ \"$inc\" : { \"size\" : 1} ," //
+ " \"$set\" : { \"nl\" : null , \"directory\" : \"/Users/Test/Desktop\" , \"foo\" : \"bar\"} , " //
+ "\"$push\" : { \"authors\" : { \"name\" : \"Sven\"}} " //
+ ", \"$pop\" : { \"authors\" : -1}}")); //
}
@Test // DATAMONGO-944
public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsingDate() {
Update update = new Update().currentDate("foo");
assertThat(update.getUpdateObject(), equalTo(new Document().append("$currentDate", new Document("foo", true))));
assertThat(update.getUpdateObject()).isEqualTo(new Document().append("$currentDate", new Document("foo", true)));
}
@Test // DATAMONGO-944
public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhenUsingDate() {
Update update = new Update().currentDate("foo").currentDate("bar");
assertThat(update.getUpdateObject(),
equalTo(new Document().append("$currentDate", new Document("foo", true).append("bar", true))));
assertThat(update.getUpdateObject())
.isEqualTo(new Document().append("$currentDate", new Document("foo", true).append("bar", true)));
}
@Test // DATAMONGO-944
public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsingTimestamp() {
Update update = new Update().currentTimestamp("foo");
assertThat(update.getUpdateObject(),
equalTo(new Document().append("$currentDate", new Document("foo", new Document("$type", "timestamp")))));
assertThat(update.getUpdateObject())
.isEqualTo(new Document().append("$currentDate", new Document("foo", new Document("$type", "timestamp"))));
}
@Test // DATAMONGO-944
public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhenUsingTimestamp() {
Update update = new Update().currentTimestamp("foo").currentTimestamp("bar");
assertThat(update.getUpdateObject(), equalTo(new Document().append("$currentDate",
new Document("foo", new Document("$type", "timestamp")).append("bar", new Document("$type", "timestamp")))));
assertThat(update.getUpdateObject()).isEqualTo(new Document().append("$currentDate",
new Document("foo", new Document("$type", "timestamp")).append("bar", new Document("$type", "timestamp"))));
}
@Test // DATAMONGO-944
public void getUpdateObjectShouldReturnCurrentDateCorrectlyWhenUsingMixedDateAndTimestamp() {
Update update = new Update().currentDate("foo").currentTimestamp("bar");
assertThat(update.getUpdateObject(), equalTo(new Document().append("$currentDate",
new Document("foo", true).append("bar", new Document("$type", "timestamp")))));
assertThat(update.getUpdateObject()).isEqualTo(new Document().append("$currentDate",
new Document("foo", true).append("bar", new Document("$type", "timestamp"))));
}
@Test // DATAMONGO-1002
public void toStringWorksForUpdateWithComplexObject() {
Update update = new Update().addToSet("key", new DateTime());
assertThat(update.toString(), is(notNullValue()));
assertThat(update.toString()).isNotNull();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1097
@@ -359,7 +360,7 @@ public class UpdateTests {
Update update = new Update().multiply("key", 10);
assertThat(update.getUpdateObject(), equalTo(new Document().append("$mul", new Document("key", 10D))));
assertThat(update.getUpdateObject()).isEqualTo(new Document().append("$mul", new Document("key", 10D)));
}
@Test // DATAMONGO-1101
@@ -367,8 +368,8 @@ public class UpdateTests {
Update update = new Update().bitwise("key").and(10L);
assertThat(update.getUpdateObject(),
equalTo(new Document().append("$bit", new Document("key", new Document("and", 10L)))));
assertThat(update.getUpdateObject())
.isEqualTo(new Document().append("$bit", new Document("key", new Document("and", 10L))));
}
@Test // DATAMONGO-1101
@@ -376,8 +377,8 @@ public class UpdateTests {
Update update = new Update().bitwise("key").or(10L);
assertThat(update.getUpdateObject(),
equalTo(new Document().append("$bit", new Document("key", new Document("or", 10L)))));
assertThat(update.getUpdateObject())
.isEqualTo(new Document().append("$bit", new Document("key", new Document("or", 10L))));
}
@Test // DATAMONGO-1101
@@ -385,8 +386,8 @@ public class UpdateTests {
Update update = new Update().bitwise("key").xor(10L);
assertThat(update.getUpdateObject(),
equalTo(new Document().append("$bit", new Document("key", new Document("xor", 10L)))));
assertThat(update.getUpdateObject())
.isEqualTo(new Document().append("$bit", new Document("key", new Document("xor", 10L))));
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-943
@@ -405,8 +406,8 @@ public class UpdateTests {
Document pullAll = DocumentTestUtils.getAsDocument(updateObject, "$pullAll");
assertThat(pullAll.get("field1"), is(notNullValue()));
assertThat(pullAll.get("field2"), is(notNullValue()));
assertThat(pullAll.get("field1")).isNotNull();
assertThat(pullAll.get("field2")).isNotNull();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1404
@@ -424,8 +425,7 @@ public class UpdateTests {
Update update = new Update().max("key", 10);
assertThat(update.getUpdateObject(),
equalTo(new Document("$max", new Document("key", 10))));
assertThat(update.getUpdateObject()).isEqualTo(new Document("$max", new Document("key", 10)));
}
@Test // DATAMONGO-1404
@@ -433,8 +433,7 @@ public class UpdateTests {
Update update = new Update().min("key", 10);
assertThat(update.getUpdateObject(),
equalTo(new Document("$min", new Document("key", 10))));
assertThat(update.getUpdateObject()).isEqualTo(new Document("$min", new Document("key", 10)));
}
@Test // DATAMONGO-1404
@@ -443,8 +442,7 @@ public class UpdateTests {
Update update = new Update().max("key", 10);
update.max("key", 99);
assertThat(update.getUpdateObject(),
equalTo(new Document("$max", new Document("key", 99))));
assertThat(update.getUpdateObject()).isEqualTo(new Document("$max", new Document("key", 99)));
}
@Test // DATAMONGO-1404
@@ -453,8 +451,7 @@ public class UpdateTests {
Update update = new Update().min("key", 10);
update.min("key", 99);
assertThat(update.getUpdateObject(),
equalTo(new Document("$min", new Document("key", 99))));
assertThat(update.getUpdateObject()).isEqualTo(new Document("$min", new Document("key", 99)));
}
@Test // DATAMONGO-1404
@@ -463,8 +460,7 @@ public class UpdateTests {
Date date = new Date();
Update update = new Update().max("key", date);
assertThat(update.getUpdateObject(),
equalTo(new Document("$max", new Document("key", date))));
assertThat(update.getUpdateObject()).isEqualTo(new Document("$max", new Document("key", date)));
}
@Test // DATAMONGO-1404
@@ -473,7 +469,62 @@ public class UpdateTests {
Date date = new Date();
Update update = new Update().min("key", date);
assertThat(update.getUpdateObject(),
equalTo(new Document("$min", new Document("key", date))));
assertThat(update.getUpdateObject()).isEqualTo(new Document("$min", new Document("key", date)));
}
@Test // DATAMONGO-1777
public void toStringShouldPrettyPrintModifiers() {
assertThat(new Update().push("key").atPosition(Position.FIRST).value("Arya").toString()).isEqualTo(
"{ \"$push\" : { \"key\" : { \"$java\" : { \"$position\" : { \"$java\" : { \"$position\" : 0} }, \"$each\" : { \"$java\" : { \"$each\" : [ \"Arya\"]} } } } } }");
}
@Test // DATAMONGO-1777
public void toStringConsidersIsolated() {
assertThat(new Update().set("key", "value").isolated().toString())
.isEqualTo("{ \"$set\" : { \"key\" : \"value\" }, \"$isolated\" : 1 }");
}
@Test // DATAMONGO-1778
public void equalsShouldConsiderModifiers() {
Update update1 = new Update().inc("version", 1).push("someField").slice(-10).each("test");
Update update2 = new Update().inc("version", 1).push("someField").slice(-10).each("test");
Update update3 = new Update().inc("version", 1).push("someField").slice(10).each("test");
assertThat(update1).isEqualTo(update2);
assertThat(update1).isNotEqualTo(update3);
}
@Test // DATAMONGO-1778
public void equalsShouldConsiderIsolated() {
Update update1 = new Update().inc("version", 1).isolated();
Update update2 = new Update().inc("version", 1).isolated();
assertThat(update1).isEqualTo(update2);
}
@Test // DATAMONGO-1778
public void hashCodeShouldConsiderModifiers() {
Update update1 = new Update().inc("version", 1).push("someField").slice(-10).each("test");
Update update2 = new Update().inc("version", 1).push("someField").slice(-10).each("test");
Update update3 = new Update().inc("version", 1).push("someField").slice(10).each("test");
assertThat(update1.hashCode()).isEqualTo(update2.hashCode());
assertThat(update1.hashCode()).isNotEqualTo(update3.hashCode());
}
@Test // DATAMONGO-1778
public void hashCodeShouldConsiderIsolated() {
Update update1 = new Update().inc("version", 1).isolated();
Update update2 = new Update().inc("version", 1).isolated();
Update update3 = new Update().inc("version", 1);
assertThat(update1.hashCode()).isEqualTo(update2.hashCode());
assertThat(update1.hashCode()).isNotEqualTo(update3.hashCode());
}
}

View File

@@ -35,6 +35,7 @@ import org.springframework.data.geo.Polygon;
import org.springframework.data.mongodb.repository.Person.Sex;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.query.Param;
import org.springframework.lang.Nullable;
/**
* Sample repository managing {@link Person} entities.
@@ -87,7 +88,7 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
* @param firstname
* @return
*/
List<Person> findByFirstnameLike(String firstname);
List<Person> findByFirstnameLike(@Nullable String firstname);
List<Person> findByFirstnameNotContains(String firstname);
@@ -232,7 +233,7 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
boolean someExistQuery(String lastname);
// DATAMONGO-770
List<Person> findByFirstnameIgnoreCase(String firstName);
List<Person> findByFirstnameIgnoreCase(@Nullable String firstName);
// DATAMONGO-770
List<Person> findByFirstnameNotIgnoreCase(String firstName);

View File

@@ -18,8 +18,9 @@ package org.springframework.data.mongodb.repository.cdi;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.apache.webbeans.cditest.CdiTestContainer;
import org.apache.webbeans.cditest.CdiTestContainerLoader;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -27,29 +28,32 @@ import org.springframework.data.mongodb.repository.Person;
/**
* Integration tests for {@link MongoRepositoryExtension}.
*
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public class CdiExtensionIntegrationTests {
static CdiTestContainer container;
static SeContainer container;
@BeforeClass
public static void setUp() throws Exception {
container = CdiTestContainerLoader.getCdiContainer();
container.bootContainer();
public static void setUp() {
container = SeContainerInitializer.newInstance() //
.disableDiscovery() //
.addPackages(CdiExtensionIntegrationTests.class) //
.initialize();
}
@AfterClass
public static void tearDown() throws Exception {
container.shutdownContainer();
public static void tearDown() {
container.close();
}
@Test
@Test // DATAMONGO-356, DATAMONGO-1785
public void bootstrapsRepositoryCorrectly() {
RepositoryClient client = container.getInstance(RepositoryClient.class);
RepositoryClient client = container.select(RepositoryClient.class).get();
CdiPersonRepository repository = client.getRepository();
assertThat(repository, is(notNullValue()));
@@ -63,11 +67,10 @@ public class CdiExtensionIntegrationTests {
assertThat(repository.findById(person.getId()).get().getId(), is(result.getId()));
}
@Test // DATAMONGO-1017
@Test // DATAMONGO-1017, DATAMONGO-1785
public void returnOneFromCustomImpl() {
RepositoryClient repositoryConsumer = container.getInstance(RepositoryClient.class);
RepositoryClient repositoryConsumer = container.select(RepositoryClient.class).get();
assertThat(repositoryConsumer.getSamplePersonRepository().returnOne(), is(1));
}
}

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

View File

@@ -6,8 +6,15 @@
* Upgrade to Java 8.
* Usage of the `Document` API instead of `DBObject`.
* <<mongo.reactive>>.
* <<mongo.reactive.repositories.infinite-streams,Tailable Cursor>> queries.
* Support for aggregation result streaming via Java 8 `Stream`.
* <<mongo.query.fluent-template-api,Fluent Collection API>> for CRUD and aggregation operations.
* Kotlin extensions for Template and Collection API.
* Integration of collations for collection and index creation and query operations.
* Query-by-Example support without type matching.
* Add support for isolation ``Update``s.
* Tooling support for null-safety via Spring's `@NonNullApi` and `@Nullable` annotations.
* Deprecated cross-store support and removed Log4j appender.
[[new-features.1-10-0]]
== What's new in Spring Data MongoDB 1.10
@@ -35,7 +42,7 @@
== What's new in Spring Data MongoDB 1.8
* `Criteria` offers support for creating `$geoIntersects`.
* Support http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/expressions.html[SpEL expressions] in `@Query`.
* Support http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#expressions[SpEL expressions] in `@Query`.
* `MongoMappingEvents` expose the collection name they are issued for.
* Improved support for `<mongo:mongo-client credentials="..." />`.
* Improved index creation failure error message.

View File

@@ -9,7 +9,7 @@ This section provides some basic introduction to Spring and Document databases.
[[get-started:first-steps:spring]]
== Knowing Spring
Spring Data uses Spring framework's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/spring-core.html[core] functionality, such as the http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/beans.html[IoC] container, http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[type conversion system], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/expressions.html[expression language], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/jmx.html[JMX integration], and portable http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html#dao-exceptions[DAO exception hierarchy]. While it is not important to know the Spring APIs, understanding the concepts behind them is. At a minimum, the idea behind IoC should be familiar for whatever IoC container you choose to use.
Spring Data uses Spring framework's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html[core] functionality, such as the http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#beans[IoC] container, http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#validation[type conversion system], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#expressions[expression language], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/integration.html#jmx[JMX integration], and portable http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/data-access.html#dao-exceptions[DAO exception hierarchy]. While it is not important to know the Spring APIs, understanding the concepts behind them is. At a minimum, the idea behind IoC should be familiar for whatever IoC container you choose to use.
The core functionality of the MongoDB support can be used directly, with no need to invoke the IoC services of the Spring Container. This is much like `JdbcTemplate` which can be used 'standalone' without any other services of the Spring container. To leverage all the features of Spring Data MongoDB, such as the repository support, you will need to configure some parts of the library using Spring.

View File

@@ -1,7 +1,7 @@
[[mongo.jmx]]
= JMX support
The JMX support for MongoDB exposes the results of executing the 'serverStatus' command on the admin database for a single MongoDB server instance. It also exposes an administrative MBean, MongoAdmin which will let you perform administrative operations such as drop or create a database. The JMX features build upon the JMX feature set available in the Spring Framework. See http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/jmx.html[here ] for more details.
The JMX support for MongoDB exposes the results of executing the 'serverStatus' command on the admin database for a single MongoDB server instance. It also exposes an administrative MBean, MongoAdmin which will let you perform administrative operations such as drop or create a database. The JMX features build upon the JMX feature set available in the Spring Framework. See http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/integration.html#jmx[here ] for more details.
[[mongodb:jmx-configuration]]
== MongoDB JMX Configuration

View File

@@ -601,7 +601,7 @@ When storing and querying your objects it is convenient to have a `MongoConverte
To selectively handle the conversion yourself, register one or more one or more `org.springframework.core.convert.converter.Converter` instances with the MongoConverter.
NOTE: Spring 3.0 introduced a core.convert package that provides a general type conversion system. This is described in detail in the Spring reference documentation section entitled http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[Spring Type Conversion].
NOTE: Spring 3.0 introduced a core.convert package that provides a general type conversion system. This is described in detail in the Spring reference documentation section entitled http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#validation[Spring Type Conversion].
The method `customConversions` in `AbstractMongoConfiguration` can be used to configure Converters. The examples <<mapping-configuration,here>> at the beginning of this chapter show how to perform the configuration using Java and XML.

View File

@@ -168,7 +168,7 @@ There is an https://github.com/spring-projects/spring-data-examples[github repos
One of the first tasks when using MongoDB and Spring is to create a `com.mongodb.MongoClient` object using the IoC container. There are two main ways to do this, either using Java based bean metadata or XML based bean metadata. These are discussed in the following sections.
NOTE: For those not familiar with how to configure the Spring container using Java based bean metadata instead of XML based metadata see the high level introduction in the reference docs http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/new-in-3.0.html#new-java-configuration[here ] as well as the detailed documentation http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/beans.html#beans-java-instantiating-container[ here].
NOTE: For those not familiar with how to configure the Spring container using Java based bean metadata instead of XML based metadata see the high level introduction in the reference docs http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/new-in-3.0.html#new-java-configuration[here ] as well as the detailed documentation http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#beans-java-instantiating-container[ here].
[[mongo.mongo-java-config]]
=== Registering a Mongo instance using Java based metadata
@@ -192,7 +192,7 @@ public class AppConfig {
----
====
This approach allows you to use the standard `com.mongodb.MongoClient` instance with the container using Spring's `MongoClientFactoryBean`. As compared to instantiating a `com.mongodb.MongoClient` instance directly, the FactoryBean has the added advantage of also providing the container with an ExceptionTranslator implementation that translates MongoDB exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation. This hierarchy and use of `@Repository` is described in http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html[Spring's DAO support features].
This approach allows you to use the standard `com.mongodb.MongoClient` instance with the container using Spring's `MongoClientFactoryBean`. As compared to instantiating a `com.mongodb.MongoClient` instance directly, the FactoryBean has the added advantage of also providing the container with an ExceptionTranslator implementation that translates MongoDB exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation. This hierarchy and use of `@Repository` is described in http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/data-access.html[Spring's DAO support features].
An example of a Java based bean metadata that supports exception translation on `@Repository` annotated classes is shown below:
@@ -1483,7 +1483,7 @@ include::query-by-example.adoc[leveloffset=+1]
You can query MongoDB using Map-Reduce which is useful for batch processing, data aggregation, and for when the query language doesn't fulfill your needs.
Spring provides integration with MongoDB's map reduce by providing methods on MongoOperations to simplify the creation and execution of Map-Reduce operations. It can convert the results of a Map-Reduce operation to a POJO also integrates with Spring's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/resources.html[Resource abstraction] abstraction. This will let you place your JavaScript files on the file system, classpath, http server or any other Spring Resource implementation and then reference the JavaScript resources via an easy URI style syntax, e.g. 'classpath:reduce.js;. Externalizing JavaScript code in files is often preferable to embedding them as Java strings in your code. Note that you can still pass JavaScript code as Java strings if you prefer.
Spring provides integration with MongoDB's map reduce by providing methods on MongoOperations to simplify the creation and execution of Map-Reduce operations. It can convert the results of a Map-Reduce operation to a POJO also integrates with Spring's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#resources[Resource abstraction] abstraction. This will let you place your JavaScript files on the file system, classpath, http server or any other Spring Resource implementation and then reference the JavaScript resources via an easy URI style syntax, e.g. 'classpath:reduce.js;. Externalizing JavaScript code in files is often preferable to embedding them as Java strings in your code. Note that you can still pass JavaScript code as Java strings if you prefer.
[[mongo.mapreduce.example]]
=== Example Usage
@@ -1633,7 +1633,7 @@ scriptOps.call("echo", "execute script via name"); <3>
As an alternative to using Map-Reduce to perform data aggregation, you can use the http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group[`group` operation] which feels similar to using SQL's group by query style, so it may feel more approachable vs. using Map-Reduce. Using the group operations does have some limitations, for example it is not supported in a shared environment and it returns the full result set in a single BSON object, so the result should be small, less than 10,000 keys.
Spring provides integration with MongoDB's group operation by providing methods on MongoOperations to simplify the creation and execution of group operations. It can convert the results of the group operation to a POJO and also integrates with Spring's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/resources.html[Resource abstraction] abstraction. This will let you place your JavaScript files on the file system, classpath, http server or any other Spring Resource implementation and then reference the JavaScript resources via an easy URI style syntax, e.g. 'classpath:reduce.js;. Externalizing JavaScript code in files if often preferable to embedding them as Java strings in your code. Note that you can still pass JavaScript code as Java strings if you prefer.
Spring provides integration with MongoDB's group operation by providing methods on MongoOperations to simplify the creation and execution of group operations. It can convert the results of the group operation to a POJO and also integrates with Spring's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#resources[Resource abstraction] abstraction. This will let you place your JavaScript files on the file system, classpath, http server or any other Spring Resource implementation and then reference the JavaScript resources via an easy URI style syntax, e.g. 'classpath:reduce.js;. Externalizing JavaScript code in files if often preferable to embedding them as Java strings in your code. Note that you can still pass JavaScript code as Java strings if you prefer.
[[mongo.group.example]]
=== Example Usage
@@ -1933,7 +1933,7 @@ Have a look at an example in more context in <<mongo.aggregation.examples.exampl
| { $not : [$a] }
|===
Next to the transformations shown in <<Supported SpEL transformations>> it is possible to use standard SpEL operations like `new` to eg. create arrays and reference expressions via their name followed by the arguments to use in brackets.
Next to the transformations shown in Supported SpEL transformations it is possible to use standard SpEL operations like `new` to eg. create arrays and reference expressions via their name followed by the arguments to use in brackets.
[source,java]
----
@@ -2235,7 +2235,7 @@ In order to have more fine-grained control over the mapping process you can regi
The `MappingMongoConverter` checks to see if there are any Spring converters that can handle a specific class before attempting to map the object itself. To 'hijack' the normal mapping strategies of the `MappingMongoConverter`, perhaps for increased performance or other custom mapping needs, you first need to create an implementation of the Spring `Converter` interface and then register it with the MappingConverter.
NOTE: For more information on the Spring type conversion service see the reference docs http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[here].
NOTE: For more information on the Spring type conversion service see the reference docs http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#validation[here].
[[mongo.custom-converters.writer]]
=== Saving using a registered Spring Converter
@@ -2477,7 +2477,7 @@ NOTE: Lifecycle events are only emitted for root level types. Complex types used
The Spring framework provides exception translation for a wide variety of database and mapping technologies. This has traditionally been for JDBC and JPA. The Spring support for MongoDB extends this feature to the MongoDB Database by providing an implementation of the `org.springframework.dao.support.PersistenceExceptionTranslator` interface.
The motivation behind mapping to Spring's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html#dao-exceptions[consistent data access exception hierarchy] is that you are then able to write portable and descriptive exception handling code without resorting to coding against http://www.mongodb.org/about/contributors/error-codes/[MongoDB error codes]. All of Spring's data access exceptions are inherited from the root `DataAccessException` class so you can be sure that you will be able to catch all database related exception within a single try-catch block. Note, that not all exceptions thrown by the MongoDB driver inherit from the MongoException class. The inner exception and message are preserved so no information is lost.
The motivation behind mapping to Spring's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/data-access.html#dao-exceptions[consistent data access exception hierarchy] is that you are then able to write portable and descriptive exception handling code without resorting to coding against MongoDB error codes. All of Spring's data access exceptions are inherited from the root `DataAccessException` class so you can be sure that you will be able to catch all database related exception within a single try-catch block. Note, that not all exceptions thrown by the MongoDB driver inherit from the MongoException class. The inner exception and message are preserved so no information is lost.
Some of the mappings performed by the `MongoExceptionTranslator` are: com.mongodb.Network to DataAccessResourceFailureException and `MongoException` error codes 1003, 12001, 12010, 12011, 12012 to `InvalidDataAccessApiUsageException`. Look into the implementation for more details on the mapping.

View File

@@ -17,8 +17,8 @@ Spring Data's Repository abstraction is a dynamic API, mostly defined by you and
* `ReactiveCrudRepository`
* `ReactiveSortingRepository`
* `RxJava1CrudRepository`
* `RxJava1SortingRepository`
* `RxJava2CrudRepository`
* `RxJava2SortingRepository`
Spring Data converts reactive wrapper types behind the scenes so that you can stick to your favorite composition library.

View File

@@ -164,7 +164,7 @@ public class AppConfig {
This approach allows you to use the standard `com.mongodb.reactivestreams.client.MongoClient` API that you may already be used to using.
An alternative is to register an instance of `com.mongodb.reactivestreams.client.MongoClient` instance with the container using Spring's `ReactiveMongoClientFactoryBean`. As compared to instantiating a `com.mongodb.reactivestreams.client.MongoClient` instance directly, the FactoryBean approach has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates MongoDB exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation. This hierarchy and use of `@Repository` is described in http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html[Spring's DAO support features].
An alternative is to register an instance of `com.mongodb.reactivestreams.client.MongoClient` instance with the container using Spring's `ReactiveMongoClientFactoryBean`. As compared to instantiating a `com.mongodb.reactivestreams.client.MongoClient` instance directly, the FactoryBean approach has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates MongoDB exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation. This hierarchy and use of `@Repository` is described in http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/data-access.html[Spring's DAO support features].
An example of a Java based bean metadata that supports exception translation on `@Repository` annotated classes is shown below:
@@ -192,7 +192,7 @@ public class AppConfig {
To access the `com.mongodb.reactivestreams.client.MongoClient` object created by the `ReactiveMongoClientFactoryBean` in other `@Configuration` or your own classes, just obtain the `MongoClient` from the context.
[[mongo.mongo-db-factory]]
[[mongo.reactive.mongo-db-factory]]
=== The ReactiveMongoDatabaseFactory interface
While `com.mongodb.reactivestreams.client.MongoClient` is the entry point to the reactive MongoDB driver API, connecting to a specific MongoDB database instance requires additional information such as the database name. With that information you can obtain a `com.mongodb.reactivestreams.client.MongoDatabase` object and access all the functionality of a specific MongoDB database instance. Spring provides the `org.springframework.data.mongodb.core.ReactiveMongoDatabaseFactory` interface shown below to bootstrap connectivity to the database.
@@ -252,7 +252,7 @@ public class MongoApp {
The use of `SimpleMongoDbFactory` is the only difference between the listing shown in the <<mongodb-reactive-getting-started,getting started section>>.
[[mongo.mongo-db-factory-java]]
[[mongo.reactive.mongo-db-factory-java]]
=== Registering a ReactiveMongoDatabaseFactory instance using Java based metadata
To register a `ReactiveMongoDatabaseFactory` instance with the container, you write code much like what was highlighted in the previous code listing. A simple example is shown below

View File

@@ -1,6 +1,21 @@
Spring Data MongoDB Changelog
=============================
Changes in version 2.0.0.RELEASE (2017-10-02)
---------------------------------------------
* DATAMONGO-1791 - Adapt to changed Spring Framework 5 documentation structure.
* DATAMONGO-1787 - Add explicit automatic module name for Java 9.
* DATAMONGO-1786 - Adapt tests to nullability enforcement in repository query methods.
* DATAMONGO-1785 - Upgrade to OpenWebBeans 2.0.1.
* DATAMONGO-1784 - Add support for AggregationExpression in GroupOperation.sum.
* DATAMONGO-1782 - CyclicPropertyReferenceException on index resolution.
* DATAMONGO-1781 - Update what's new in reference documentation.
* DATAMONGO-1779 - Query.limit(N) on empty query is not applied.
* DATAMONGO-1778 - Update.equals(…) fails to recognize an equal Update object.
* DATAMONGO-1777 - Update.toString(…) does not pretty-print modifiers.
* DATAMONGO-1776 - Release 2.0 GA (Kay).
Changes in version 2.0.0.RC3 (2017-09-11)
-----------------------------------------
* DATAMONGO-1774 - ReactiveMongoOperations#remove(Mono, java.lang.String) ends up in an infinite loop.

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 2.0 RC3
Spring Data MongoDB 2.0 GA
Copyright (c) [2010-2015] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").