Compare commits

...

70 Commits

Author SHA1 Message Date
Mark Paluch
2b47f44531 DATAMONGO-2109 - Release version 2.0.12 (Kay SR12). 2018-11-27 11:45:49 +01:00
Mark Paluch
79534fa426 DATAMONGO-2109 - Prepare 2.0.12 (Kay SR12). 2018-11-27 11:44:58 +01:00
Mark Paluch
b8e76ecae4 DATAMONGO-2109 - Updated changelog. 2018-11-27 11:44:52 +01:00
Mark Paluch
3e2b060611 DATAMONGO-2110 - Updated changelog. 2018-11-27 11:27:23 +01:00
Mark Paluch
4699219728 DATAMONGO-2119 - Polishing.
Convert anonymous JSON callback class into a private static one. Use an expressive Pattern constant.

Original pull request: #621.
2018-11-23 09:56:24 +01:00
Christoph Strobl
a483d95cde DATAMONGO-2119 - Allow SpEL usage for annotated $regex query.
Original pull request: #621.
2018-11-23 09:56:24 +01:00
Oliver Drotbohm
84f35f5655 DATAMONGO-2135 - Polishing. 2018-11-15 15:30:41 +01:00
Oliver Drotbohm
2ec0f93325 DATAMONGO-2135 - Default to intermediate List for properties typed to Collection.
We now defensively create a List rather than a LinkedHashSet (which Spring's CollectionFactory.createCollection(…) defaults to) to make sure we're not accidentally dropping values that are considered equal according to their Java class definition.
2018-11-15 15:30:30 +01:00
Mark Paluch
48f9422a66 DATAMONGO-2107 - Updated changelog. 2018-10-29 14:30:33 +01:00
Mark Paluch
a2349405af DATAMONGO-2118 - Polishing.
Fix typo in reactive repositories reference documentation.

Original pull request: #611.
2018-10-26 10:08:07 +02:00
Mona Mohamadinia
e12ab354f7 DATAMONGO-2118 - Fix typo in repositories reference documentation.
Original pull request: #611.
2018-10-26 10:08:07 +02:00
Mark Paluch
04b20fa9c0 DATAMONGO-2098 - Polishing.
Annotate methods and parameters with Nullable. Use diamond syntax where appropriate.

Original pull request: #612.
2018-10-25 15:35:31 +02:00
Zied Yaich
81c46f04d6 DATAMONGO-2098 - Fix typo in MappingMongoConverterParser method.
Original pull request: #612.
2018-10-25 15:35:31 +02:00
Mark Paluch
ce905c80fe DATAMONGO-2083 - Updated changelog. 2018-10-15 14:19:05 +02:00
Mark Paluch
831e4f9ef1 DATAMONGO-2084 - After release cleanups. 2018-10-15 12:28:13 +02:00
Mark Paluch
5ce293a871 DATAMONGO-2084 - Prepare next development iteration. 2018-10-15 12:28:12 +02:00
Mark Paluch
bcd61f0dae DATAMONGO-2084 - Release version 2.0.11 (Kay SR11). 2018-10-15 12:00:38 +02:00
Mark Paluch
478594c3ca DATAMONGO-2084 - Prepare 2.0.11 (Kay SR11). 2018-10-15 11:59:39 +02:00
Mark Paluch
bb101d5e18 DATAMONGO-2084 - Updated changelog. 2018-10-15 11:59:31 +02:00
Mark Paluch
a5bc7a2a08 DATAMONGO-2094 - Updated changelog. 2018-10-15 11:37:27 +02:00
Christoph Strobl
6720967e19 DATAMONGO-2101 - Fix DBObject to GeoJson conversion.
Querydsl still wraps MongoDB data in DBObject which causes trouble with the registered converters that deal with Document to entity conversion. Therefore we now try to extract the argument map from the DBObject transferring it to Document in order to have the converters kick in where applicable.

Original pull request: #614.
2018-10-08 09:40:23 +02:00
Mark Paluch
99a4661e81 DATAMONGO-2096 - Polishing.
Migrate assertions to AssertJ.

Original pull request: #613.
2018-10-05 15:02:45 +02:00
Christoph Strobl
338bc30b96 DATAMONGO-2096 - Fix target field name for GraphLookup aggregation operation.
We now make sure to use the target field name instead of the alias when processing GraphLookupOperation.

Original pull request: #613.
2018-10-05 15:02:45 +02:00
Mark Paluch
7fa3f0068b DATAMONGO-2061 - Updated changelog. 2018-09-21 08:13:15 -04:00
Khaled Baklouti
abc74fdcc6 DATAMONGO-2087 - Fix typo in MongoRepository.
Original Pull Request: #610
2018-09-20 09:18:44 +02:00
Mark Paluch
3a895588c8 DATAMONGO-2086 - Fix Fluent API Kotlin extension generics to allow projections.
We now fixed Kotlin extension generics to properly use projections by ignoring the source type of the Fluent API object. Previously, the source and target type were linked which prevented the use of a different result type.
2018-09-17 13:59:45 +02:00
Mark Paluch
f79d98ce23 DATAMONGO-2034 - After release cleanups. 2018-09-10 13:52:27 +02:00
Mark Paluch
2bcc0d8185 DATAMONGO-2034 - Prepare next development iteration. 2018-09-10 13:52:26 +02:00
Mark Paluch
c8846d3d1c DATAMONGO-2034 - Release version 2.0.10 (Kay SR10). 2018-09-10 12:52:18 +02:00
Mark Paluch
a4835c8fcf DATAMONGO-2034 - Prepare 2.0.10 (Kay SR10). 2018-09-10 12:51:27 +02:00
Mark Paluch
7875c8399f DATAMONGO-2034 - Updated changelog. 2018-09-10 12:51:22 +02:00
Mark Paluch
9046857721 DATAMONGO-2035 - Updated changelog. 2018-09-10 10:20:56 +02:00
Oliver Gierke
e8bb63c9f7 DATAMONGO-2076 - Fixed attribute substitution in reactive MongoDB section.
We now redeclare the Asciidoctor Maven plugin to register the store specific attributes. Apparently they must not contain dots, so we replaced them with dashes.
2018-08-30 11:45:08 +02:00
Oliver Gierke
b431a56a95 DATAMONGO-2076 - Fixed attribute substitution in getting started section. 2018-08-30 09:32:05 +02:00
Oliver Gierke
dc820017e0 DATAMONGO-2033 - Updated changelog. 2018-08-20 11:07:56 +02:00
Oliver Gierke
34ce87b80c DATAMONGO-2046 - Performance improvements in mapping and conversion subsystem.
In MappingMongoConverter, we now avoid the creation of a ParameterValueProvider for parameter-less constructors. We also skip property population if entity can be constructed entirely through constructor creation. Replaced the lambda in MappingMongoConverter.readAndPopulateIdentifier(…) with direct call to ….readIdValue(…). Objectpath now uses decomposed ObjectPathItems to avoid array copying and creation. It now stores a reference to its parent and ObjectPathItem fields are now merged into ObjectPath, which reduces the number of created objects during reads.

Extended CachingMongoPersistentProperty with DBRef caching. Turned key access in DocumentAccessor into an optimistic lookup. DbRefResolverCallbacks are now created lazily.

Related tickets: DATACMNS-1366.
Original pull request: #602.
2018-08-15 16:12:22 +02:00
Mark Paluch
9098d509a5 DATAMONGO-2055 - Polishing.
Move test to UpdateMapperUnitTests.

Original pull request: #600.
2018-08-15 15:59:55 +02:00
Christoph Strobl
861c8279a3 DATAMONGO-2055 - Allow position modifier to be negative using push at position on Update.
Original pull request: #600.
2018-08-15 15:53:59 +02:00
Mark Paluch
e545787e7e DATAMONGO-2050 - Polishing.
Tweak Javadoc.

Original pull request: #596.
2018-08-15 15:18:19 +02:00
Christoph Strobl
38ccdc5dfc DATAMONGO-2050 - Polishing.
Move to AssertJ.

Original pull request: #596.
2018-08-15 15:05:06 +02:00
Christoph Strobl
7a34cc73d8 DATAMONGO-2050 - Allow to specify the index to use for $geoNear aggregation operation.
Original pull request: #596.
2018-08-15 15:05:04 +02:00
Mark Paluch
ba6fa834e5 DATAMONGO-2051 - Polishing.
Use method argument types to avoid false positives with different method signatures.

Original pull request: #597.
Related pull request: #598.
2018-08-14 16:37:32 +02:00
Christoph Strobl
7100cd17be DATAMONGO-2051 - Add support for SCRAM-SHA-256 authentication mechanism to MongoCredentialPropertyEditor.
Original pull request: #597.
Related pull request: #598.
2018-08-14 16:33:39 +02:00
Mark Paluch
7c65472e2d DATAMONGO-2049 - Polishing.
Add static import for assertThat(…).

Original pull request: #594.
2018-08-14 10:51:41 +02:00
Christoph Strobl
f98f586a23 DATAMONGO-2049 - Add support for $ltrim, $rtrim, and $trim.
Original pull request: #594.
2018-08-14 10:51:41 +02:00
Mark Paluch
19b5b6b6f0 DATAMONGO-2048 - Polishing.
Javadoc tweaks.

Original pull request: #595.
2018-08-13 16:00:40 +02:00
Christoph Strobl
b9ffa9b89d DATAMONGO-2048 - Add support for MongoDB 4.0 $convert aggregation operator.
We now support the following type conversion aggregation operators:

* $convert
* $toBool
* $toDate
* $toDecimal
* $toDouble
* $toInt
* $toLong
* $toObjectId
* $toString

Original pull request: #595.
2018-08-13 16:00:40 +02:00
Mark Paluch
3ba589072f DATAMONGO-2047 - Polishing.
Retain previous options when calling withTimezone(…)/onNull…(…). Add tests. Javadoc.

Original pull request: #593.
2018-08-13 13:27:27 +02:00
Christoph Strobl
e237c5dfc4 DATAMONGO-2047 - Update $dateToString and $dateFromString aggregation operators to match MongoDB 4.0 changes.
We added the format and onNull options to DateFromString and changed format to an optional parameter.

Original pull request: #593.
2018-08-13 13:27:26 +02:00
Mark Paluch
ecb560cdbc DATAMONGO-2045 - Polishing.
Use diamond syntax where possible. Add initial size to HashMap instances with known number of elements. Fix typos in private constant names. Fix duplicate error code ids.

Original pull request: #592.
2018-08-13 10:31:28 +02:00
Mark Paluch
fc4a21775a DATAMONGO-2043 - Polishing.
Slightly tweak Javadoc.

Original pull request: #589.
2018-08-08 11:01:14 +02:00
Christoph Strobl
ae62e70c52 DATAMONGO-2043 - Omit type hint when mapping simple types.
Original pull request: #589.
2018-08-08 11:01:09 +02:00
Christoph Strobl
f83622709d DATAMONGO-2027 - Polishing.
Remove duplicate tests and fix assertions on existing ones. Move tests over to AssertJ and fix output database not applied correctly.

Original Pull Request: #588
2018-08-07 13:37:22 +02:00
Mark Paluch
83d218081c DATAMONGO-2027 - Consider MapReduce output type.
We now consider the output type (collection output) when rendering the MapReduce command. Previously, all output was returned inline without storing the results in the configured collection.

Original Pull Request: #588

# Conflicts:
#	spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
#	spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/ReactiveMapReduceTests.java
2018-08-07 13:20:46 +02:00
Mark Paluch
70fe406602 DATAMONGO-2006 - Updated changelog. 2018-07-27 11:45:23 +02:00
Mark Paluch
18046e9040 DATAMONGO-2007 - After release cleanups. 2018-07-26 15:23:24 +02:00
Mark Paluch
69310552e3 DATAMONGO-2007 - Prepare next development iteration. 2018-07-26 15:23:22 +02:00
Mark Paluch
b8f093269d DATAMONGO-2007 - Release version 2.0.9 (Kay SR9). 2018-07-26 14:44:00 +02:00
Mark Paluch
172db96fea DATAMONGO-2007 - Prepare 2.0.9 (Kay SR9). 2018-07-26 14:43:06 +02:00
Mark Paluch
c8381c734b DATAMONGO-2007 - Updated changelog. 2018-07-26 14:42:54 +02:00
Mark Paluch
bf82964474 DATAMONGO-1982 - Updated changelog. 2018-07-26 14:03:20 +02:00
Mark Paluch
2d0495874f DATAMONGO-2029 - Encode collections of UUID and byte array query method arguments to their binary form.
We now convert collections that only contain UUID or byte array items to a BSON list that contains the encoded form of these items. Previously, we only converted single UUID and byte arrays into $binary so lists rendered to e.g. $uuid which does not work for queries.

Encoding is now encapsulated in strategy objects that implement the encoding only for their type. This allows to break up the conditional flow and improve organization of responsibilities.
2018-07-25 15:16:15 +02:00
Mark Paluch
82c91cbb71 DATAMONGO-2030 - Reinstantiate existsBy queries for reactive repositories.
We now support existsBy queries for reactive repositories to align with blocking repository support. ExistsBy support got lost during merging and is now back in place.

Extract boolean flag counting into BooleanUtil.
2018-07-23 16:34:12 +02:00
Christoph Strobl
4d309bd7f0 DATAMONGO-2011 - Relax type check when mapping collections.
Original pull request: #587.
2018-07-13 12:55:07 +02:00
Mark Paluch
6f011b0fa1 DATAMONGO-2021 - Polishing.
Adapt getResources(…) to use the file id and no longer the file name when opening a download stream. Add author tag.

Original pull request: #581.
2018-07-06 13:12:36 +02:00
Niklas Helge Hanft
1a3b9e3c42 DATAMONGO-2021 - Use getObjectId() instead of getFilename() for opening the GridFS download stream.
Using the file name leads to duplicate resource streams as file names are not unique therefore we're using the file's ObjectId to lookup the file content.

Original pull request: #581.
2018-07-06 13:12:36 +02:00
Mark Paluch
5a37468103 DATAMONGO-2016 - Polishing.
Fail gracefully if query string parameter has no value. Reformat test. Convert assertions to AssertJ.

Original pull request: #578.
2018-07-04 11:25:38 +02:00
Stephen Tyler Conrad
d4b0963550 DATAMONGO-2016 - Fix username/password extraction in MongoCredentialPropertyEditor.
MongoCredentialPropertyEditor inspects now the connection URI for the appropriate delimiter tokens. Previously, inspection used the char questionmark for username/password delimiter inspection.

Original pull request: #578.
2018-07-04 11:25:35 +02:00
Mark Paluch
468c497525 DATAMONGO-1969 - After release cleanups. 2018-06-13 21:24:35 +02:00
Mark Paluch
4562f39d7a DATAMONGO-1969 - Prepare next development iteration. 2018-06-13 21:24:33 +02:00
57 changed files with 3070 additions and 510 deletions

24
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.12.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.8.RELEASE</version>
<version>2.0.12.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.8.RELEASE</springdata.commons>
<springdata.commons>2.0.12.RELEASE</springdata.commons>
<mongo>3.5.0</mongo>
<mongo.reactivestreams>1.6.0</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
@@ -138,6 +138,24 @@
</modules>
</profile>
<profile>
<id>distribute</id>
<build>
<plugins>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<configuration>
<attributes>
<mongo-reactivestreams>${mongo.reactivestreams}</mongo-reactivestreams>
<reactor>${reactor}</reactor>
</attributes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.12.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.8.RELEASE</version>
<version>2.0.12.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -50,7 +50,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.0.8.RELEASE</version>
<version>2.0.12.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.8.RELEASE</version>
<version>2.0.12.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

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

View File

@@ -60,6 +60,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -75,6 +76,7 @@ import org.w3c.dom.Element;
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @author Zied Yaich
*/
public class MappingMongoConverterParser implements BeanDefinitionParser {
@@ -159,6 +161,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
return null;
}
@Nullable
private BeanDefinition potentiallyCreateValidatingMongoEventListener(Element element, ParserContext parserContext) {
String disableValidation = element.getAttribute("disable-validation");
@@ -180,6 +183,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
return null;
}
@Nullable
private RuntimeBeanReference getValidator(Object source, ParserContext parserContext) {
if (!JSR_303_PRESENT) {
@@ -197,7 +201,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
}
public static String potentiallyCreateMappingContext(Element element, ParserContext parserContext,
BeanDefinition conversionsDefinition, String converterId) {
@Nullable BeanDefinition conversionsDefinition, @Nullable String converterId) {
String ctxRef = element.getAttribute("mapping-context-ref");
@@ -211,7 +215,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
BeanDefinitionBuilder mappingContextBuilder = BeanDefinitionBuilder
.genericBeanDefinition(MongoMappingContext.class);
Set<String> classesToAdd = getInititalEntityClasses(element);
Set<String> classesToAdd = getInitialEntityClasses(element);
if (classesToAdd != null) {
mappingContextBuilder.addPropertyValue("initialEntitySet", classesToAdd);
@@ -262,6 +266,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
}
}
@Nullable
private BeanDefinition getCustomConversions(Element element, ParserContext parserContext) {
List<Element> customConvertersElements = DomUtils.getChildElementsByTagName(element, "custom-converters");
@@ -269,7 +274,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
if (customConvertersElements.size() == 1) {
Element customerConvertersElement = customConvertersElements.get(0);
ManagedList<BeanMetadataElement> converterBeans = new ManagedList<BeanMetadataElement>();
ManagedList<BeanMetadataElement> converterBeans = new ManagedList<>();
List<Element> converterElements = DomUtils.getChildElementsByTagName(customerConvertersElement, "converter");
if (converterElements != null) {
@@ -285,9 +290,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
provider.addExcludeFilter(new NegatingFilter(new AssignableTypeFilter(Converter.class),
new AssignableTypeFilter(GenericConverter.class)));
for (BeanDefinition candidate : provider.findCandidateComponents(packageToScan)) {
converterBeans.add(candidate);
}
converterBeans.addAll(provider.findCandidateComponents(packageToScan));
}
BeanDefinitionBuilder conversionsBuilder = BeanDefinitionBuilder.rootBeanDefinition(MongoCustomConversions.class);
@@ -304,7 +307,8 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
return null;
}
private static Set<String> getInititalEntityClasses(Element element) {
@Nullable
private static Set<String> getInitialEntityClasses(Element element) {
String basePackage = element.getAttribute(BASE_PACKAGE);
@@ -317,7 +321,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
Set<String> classes = new ManagedSet<String>();
Set<String> classes = new ManagedSet<>();
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
classes.add(candidate.getBeanClassName());
}
@@ -325,6 +329,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
return classes;
}
@Nullable
public BeanMetadataElement parseConverter(Element element, ParserContext parserContext) {
String converterRef = element.getAttribute("ref");
@@ -375,7 +380,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
Assert.notNull(filters, "TypeFilters must not be null");
this.delegates = new HashSet<TypeFilter>(Arrays.asList(filters));
this.delegates = new HashSet<>(Arrays.asList(filters));
}
/*

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.config;
import java.beans.PropertyEditorSupport;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -26,6 +27,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import com.mongodb.MongoCredential;
@@ -35,6 +37,8 @@ import com.mongodb.MongoCredential;
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Stephen Tyler Conrad
* @author Mark Paluch
* @since 1.7
*/
public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
@@ -98,6 +102,20 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
verifyDatabasePresent(database);
credentials.add(MongoCredential.createScramSha1Credential(userNameAndPassword[0], database,
userNameAndPassword[1].toCharArray()));
} else if ("SCRAM-SHA-256".equals(authMechanism)) {
Method createScramSha256Credential = ReflectionUtils.findMethod(MongoCredential.class,
"createScramSha256Credential", String.class, String.class, char[].class);
if (createScramSha256Credential == null) {
throw new IllegalArgumentException(
"SCRAM-SHA-256 auth mechanism is available as of MongoDB 4 and MongoDB Java Driver 3.8! Please make sure to use at least those versions.");
}
verifyUsernameAndPasswordPresent(userNameAndPassword);
verifyDatabasePresent(database);
credentials.add(MongoCredential.class.cast(ReflectionUtils.invokeMethod(createScramSha256Credential, null,
userNameAndPassword[0], database, userNameAndPassword[1].toCharArray())));
} else {
throw new IllegalArgumentException(
String.format("Cannot create MongoCredentials for unknown auth mechanism '%s'!", authMechanism));
@@ -164,7 +182,7 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
private static Properties extractOptions(String text) {
int optionsSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
int dbSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
int dbSeparationIndex = text.lastIndexOf(DATABASE_DELIMITER);
if (optionsSeparationIndex == -1 || dbSeparationIndex > optionsSeparationIndex) {
return new Properties();
@@ -173,7 +191,13 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
Properties properties = new Properties();
for (String option : text.substring(optionsSeparationIndex + 1).split(OPTION_VALUE_DELIMITER)) {
String[] optionArgs = option.split("=");
if (optionArgs.length == 1) {
throw new IllegalArgumentException(String.format("Query parameter '%s' has no value!", optionArgs[0]));
}
properties.put(optionArgs[0], optionArgs[1]);
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -51,17 +52,17 @@ import com.mongodb.bulk.BulkWriteError;
*/
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
private static final Set<String> DULICATE_KEY_EXCEPTIONS = new HashSet<String>(
private static final Set<String> DUPLICATE_KEY_EXCEPTIONS = new HashSet<>(
Arrays.asList("MongoException.DuplicateKey", "DuplicateKeyException"));
private static final Set<String> RESOURCE_FAILURE_EXCEPTIONS = new HashSet<String>(
private static final Set<String> RESOURCE_FAILURE_EXCEPTIONS = new HashSet<>(
Arrays.asList("MongoException.Network", "MongoSocketException", "MongoException.CursorNotFound",
"MongoCursorNotFoundException", "MongoServerSelectionException", "MongoTimeoutException"));
private static final Set<String> RESOURCE_USAGE_EXCEPTIONS = new HashSet<String>(
Arrays.asList("MongoInternalException"));
private static final Set<String> RESOURCE_USAGE_EXCEPTIONS = new HashSet<>(
Collections.singletonList("MongoInternalException"));
private static final Set<String> DATA_INTEGRETY_EXCEPTIONS = new HashSet<String>(
private static final Set<String> DATA_INTEGRITY_EXCEPTIONS = new HashSet<>(
Arrays.asList("WriteConcernException", "MongoWriteException", "MongoBulkWriteException"));
/*
@@ -79,7 +80,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass()));
if (DULICATE_KEY_EXCEPTIONS.contains(exception)) {
if (DUPLICATE_KEY_EXCEPTIONS.contains(exception)) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
@@ -91,7 +92,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
}
if (DATA_INTEGRETY_EXCEPTIONS.contains(exception)) {
if (DATA_INTEGRITY_EXCEPTIONS.contains(exception)) {
if (ex instanceof MongoServerException) {
if (((MongoServerException) ex).getCode() == 11000) {

View File

@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import com.mongodb.client.model.MapReduceAction;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NonNull;
@@ -1715,18 +1716,32 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (!CollectionUtils.isEmpty(mapReduceOptions.getScopeVariables())) {
result = result.scope(new Document(mapReduceOptions.getScopeVariables()));
}
if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) {
result = result.limit(mapReduceOptions.getLimit());
}
if (mapReduceOptions.getFinalizeFunction().filter(StringUtils::hasText).isPresent()) {
result = result.finalizeFunction(mapReduceOptions.getFinalizeFunction().get());
}
if (mapReduceOptions.getJavaScriptMode() != null) {
result = result.jsMode(mapReduceOptions.getJavaScriptMode());
}
if (mapReduceOptions.getOutputSharded().isPresent()) {
result = result.sharded(mapReduceOptions.getOutputSharded().get());
}
if (StringUtils.hasText(mapReduceOptions.getOutputCollection()) && !mapReduceOptions.usesInlineOutput()) {
result = result.collectionName(mapReduceOptions.getOutputCollection())
.action(mapReduceOptions.getMapReduceAction());
if (mapReduceOptions.getOutputDatabase().isPresent()) {
result = result.databaseName(mapReduceOptions.getOutputDatabase().get());
}
}
}
result = collation.map(Collation::toMongoCollation).map(result::collation).orElse(result);

View File

@@ -0,0 +1,670 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.Collections;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Gateway to {@literal convert} aggregation operations.
*
* @author Christoph Strobl
* @since 2.0.10
*/
public class ConvertOperators {
/**
* Take the field referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static ConvertOperatorFactory valueOf(String fieldReference) {
return new ConvertOperatorFactory(fieldReference);
}
/**
* Take the value resulting from the given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static ConvertOperatorFactory valueOf(AggregationExpression expression) {
return new ConvertOperatorFactory(expression);
}
/**
* @author Christoph Strobl
*/
public static class ConvertOperatorFactory {
private final @Nullable String fieldReference;
private final @Nullable AggregationExpression expression;
/**
* Creates new {@link ConvertOperatorFactory} for given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
*/
public ConvertOperatorFactory(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.fieldReference = fieldReference;
this.expression = null;
}
/**
* Creates new {@link ConvertOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
public ConvertOperatorFactory(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
this.fieldReference = null;
this.expression = expression;
}
/**
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
* specified by the given {@code stringTypeIdentifier}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param stringTypeIdentifier must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert convertTo(String stringTypeIdentifier) {
return createConvert().to(stringTypeIdentifier);
}
/**
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
* specified by the given {@code numericTypeIdentifier}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param numericTypeIdentifier must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert convertTo(int numericTypeIdentifier) {
return createConvert().to(numericTypeIdentifier);
}
/**
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
* specified by the value of the given {@link Field field reference}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert convertToTypeOf(String fieldReference) {
return createConvert().toTypeOf(fieldReference);
}
/**
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
* specified by the given {@link AggregationExpression expression}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert convertToTypeOf(AggregationExpression expression) {
return createConvert().toTypeOf(expression);
}
/**
* Creates new {@link ToBool aggregation expression} for {@code $toBool} that converts a value to boolean. Shorthand
* for {@link #convertTo(String) #convertTo("bool")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link ToBool}.
*/
public ToBool convertToBoolean() {
return ToBool.toBoolean(valueObject());
}
/**
* Creates new {@link ToDate aggregation expression} for {@code $toDate} that converts a value to a date. Shorthand
* for {@link #convertTo(String) #convertTo("date")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link ToDate}.
*/
public ToDate convertToDate() {
return ToDate.toDate(valueObject());
}
/**
* Creates new {@link ToDecimal aggregation expression} for {@code $toDecimal} that converts a value to a decimal.
* Shorthand for {@link #convertTo(String) #convertTo("decimal")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link ToDecimal}.
*/
public ToDecimal convertToDecimal() {
return ToDecimal.toDecimal(valueObject());
}
/**
* Creates new {@link ToDouble aggregation expression} for {@code $toDouble} that converts a value to a decimal.
* Shorthand for {@link #convertTo(String) #convertTo("double")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link ToDouble}.
*/
public ToDouble convertToDouble() {
return ToDouble.toDouble(valueObject());
}
/**
* Creates new {@link ToInt aggregation expression} for {@code $toInt} that converts a value to an int. Shorthand
* for {@link #convertTo(String) #convertTo("int")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link ToInt}.
*/
public ToInt convertToInt() {
return ToInt.toInt(valueObject());
}
/**
* Creates new {@link ToInt aggregation expression} for {@code $toLong} that converts a value to a long. Shorthand
* for {@link #convertTo(String) #convertTo("long")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link ToInt}.
*/
public ToLong convertToLong() {
return ToLong.toLong(valueObject());
}
/**
* Creates new {@link ToInt aggregation expression} for {@code $toObjectId} that converts a value to a objectId. Shorthand
* for {@link #convertTo(String) #convertTo("objectId")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link ToInt}.
*/
public ToObjectId convertToObjectId() {
return ToObjectId.toObjectId(valueObject());
}
/**
* Creates new {@link ToInt aggregation expression} for {@code $toString} that converts a value to a string. Shorthand
* for {@link #convertTo(String) #convertTo("string")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link ToInt}.
*/
public ToString convertToString() {
return ToString.toString(valueObject());
}
private Convert createConvert() {
return usesFieldRef() ? Convert.convertValueOf(fieldReference) : Convert.convertValueOf(expression);
}
private Object valueObject() {
return usesFieldRef() ? Fields.field(fieldReference) : expression;
}
private boolean usesFieldRef() {
return fieldReference != null;
}
}
/**
* {@link AggregationExpression} for {@code $convert} that converts a value to a specified type. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/convert/">https://docs.mongodb.com/manual/reference/operator/aggregation/convert/</a>
* @since 2.0.10
*/
public static class Convert extends AbstractAggregationExpression {
private Convert(Object value) {
super(value);
}
/**
* Creates new {@link Convert} using the given value for the {@literal input} attribute.
*
* @param value must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public static Convert convertValue(Object value) {
return new Convert(Collections.singletonMap("input", value));
}
/**
* Creates new {@link Convert} using the value of the provided {@link Field fieldReference} as {@literal input}
* value.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public static Convert convertValueOf(String fieldReference) {
return convertValue(Fields.field(fieldReference));
}
/**
* Creates new {@link Convert} using the result of the provided {@link AggregationExpression expression} as
* {@literal input} value.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public static Convert convertValueOf(AggregationExpression expression) {
return convertValue(expression);
}
/**
* Specify the conversion target type via its {@link String} representation.
* <ul>
* <li>double</li>
* <li>string</li>
* <li>objectId</li>
* <li>bool</li>
* <li>date</li>
* <li>int</li>
* <li>long</li>
* <li>decimal</li>
* </ul>
*
* @param stringTypeIdentifier must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert to(String stringTypeIdentifier) {
return new Convert(append("to", stringTypeIdentifier));
}
/**
* Specify the conversion target type via its numeric representation.
* <dl>
* <dt>1</dt>
* <dd>double</dd>
* <dt>2</dt>
* <dd>string</li>
* <dt>7</dt>
* <dd>objectId</li>
* <dt>8</dt>
* <dd>bool</dd>
* <dt>9</dt>
* <dd>date</dd>
* <dt>16</dt>
* <dd>int</dd>
* <dt>18</dt>
* <dd>long</dd>
* <dt>19</dt>
* <dd>decimal</dd>
* </dl>
*
* @param numericTypeIdentifier must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert to(int numericTypeIdentifier) {
return new Convert(append("to", numericTypeIdentifier));
}
/**
* Specify the conversion target type via the value of the given field.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert toTypeOf(String fieldReference) {
return new Convert(append("to", Fields.field(fieldReference)));
}
/**
* Specify the conversion target type via the value of the given {@link AggregationExpression expression}.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert toTypeOf(AggregationExpression expression) {
return new Convert(append("to", expression));
}
/**
* Optionally specify the value to return on encountering an error during conversion.
*
* @param value must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert onErrorReturn(Object value) {
return new Convert(append("onError", value));
}
/**
* Optionally specify the field holding the value to return on encountering an error during conversion.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert onErrorReturnValueOf(String fieldReference) {
return onErrorReturn(Fields.field(fieldReference));
}
/**
* Optionally specify the expression to evaluate and return on encountering an error during conversion.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert onErrorReturnValueOf(AggregationExpression expression) {
return onErrorReturn(expression);
}
/**
* Optionally specify the value to return when the input is {@literal null} or missing.
*
* @param value must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert onNullReturn(Object value) {
return new Convert(append("onNull", value));
}
/**
* Optionally specify the field holding the value to return when the input is {@literal null} or missing.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert onNullReturnValueOf(String fieldReference) {
return onNullReturn(Fields.field(fieldReference));
}
/**
* Optionally specify the expression to evaluate and return when the input is {@literal null} or missing.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Convert}.
*/
public Convert onNullReturnValueOf(AggregationExpression expression) {
return onNullReturn(expression);
}
@Override
protected String getMongoMethod() {
return "$convert";
}
}
/**
* {@link AggregationExpression} for {@code $toBool} that converts a value to {@literal boolean}. Shorthand for
* {@link Convert#to(String) Convert#to("bool")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toBool/">https://docs.mongodb.com/manual/reference/operator/aggregation/toBool/</a>
* @since 2.0.10
*/
public static class ToBool extends AbstractAggregationExpression {
private ToBool(Object value) {
super(value);
}
/**
* Creates new {@link ToBool} using the given value as input.
*
* @param value must not be {@literal null}.
* @return new instance of {@link ToBool}.
*/
public static ToBool toBoolean(Object value) {
return new ToBool(value);
}
@Override
protected String getMongoMethod() {
return "$toBool";
}
}
/**
* {@link AggregationExpression} for {@code $toDate} that converts a value to {@literal date}. Shorthand for
* {@link Convert#to(String) Convert#to("date")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDate/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDate/</a>
* @since 2.0.10
*/
public static class ToDate extends AbstractAggregationExpression {
private ToDate(Object value) {
super(value);
}
/**
* Creates new {@link ToDate} using the given value as input.
*
* @param value must not be {@literal null}.
* @return new instance of {@link ToDate}.
*/
public static ToDate toDate(Object value) {
return new ToDate(value);
}
@Override
protected String getMongoMethod() {
return "$toDate";
}
}
/**
* {@link AggregationExpression} for {@code $toDecimal} that converts a value to {@literal decimal}. Shorthand for
* {@link Convert#to(String) Convert#to("decimal")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDecimal/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDecimal/</a>
* @since 2.0.10
*/
public static class ToDecimal extends AbstractAggregationExpression {
private ToDecimal(Object value) {
super(value);
}
/**
* Creates new {@link ToDecimal} using the given value as input.
*
* @param value must not be {@literal null}.
* @return new instance of {@link ToDecimal}.
*/
public static ToDecimal toDecimal(Object value) {
return new ToDecimal(value);
}
@Override
protected String getMongoMethod() {
return "$toDecimal";
}
}
/**
* {@link AggregationExpression} for {@code $toDouble} that converts a value to {@literal double}. Shorthand for
* {@link Convert#to(String) Convert#to("double")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDouble/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDouble/</a>
* @since 2.0.10
*/
public static class ToDouble extends AbstractAggregationExpression {
private ToDouble(Object value) {
super(value);
}
/**
* Creates new {@link ToDouble} using the given value as input.
*
* @param value must not be {@literal null}.
* @return new instance of {@link ToDouble}.
*/
public static ToDouble toDouble(Object value) {
return new ToDouble(value);
}
@Override
protected String getMongoMethod() {
return "$toDouble";
}
}
/**
* {@link AggregationExpression} for {@code $toInt} that converts a value to {@literal integer}. Shorthand for
* {@link Convert#to(String) Convert#to("int")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toInt/">https://docs.mongodb.com/manual/reference/operator/aggregation/toInt/</a>
* @since 2.0.10
*/
public static class ToInt extends AbstractAggregationExpression {
private ToInt(Object value) {
super(value);
}
/**
* Creates new {@link ToInt} using the given value as input.
*
* @param value must not be {@literal null}.
* @return new instance of {@link ToInt}.
*/
public static ToInt toInt(Object value) {
return new ToInt(value);
}
@Override
protected String getMongoMethod() {
return "$toInt";
}
}
/**
* {@link AggregationExpression} for {@code $toLong} that converts a value to {@literal long}. Shorthand for
* {@link Convert#to(String) Convert#to("long")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toLong/">https://docs.mongodb.com/manual/reference/operator/aggregation/toLong/</a>
* @since 2.0.10
*/
public static class ToLong extends AbstractAggregationExpression {
private ToLong(Object value) {
super(value);
}
/**
* Creates new {@link ToLong} using the given value as input.
*
* @param value must not be {@literal null}.
* @return new instance of {@link ToLong}.
*/
public static ToLong toLong(Object value) {
return new ToLong(value);
}
@Override
protected String getMongoMethod() {
return "$toLong";
}
}
/**
* {@link AggregationExpression} for {@code $toObjectId} that converts a value to {@literal objectId}. Shorthand for
* {@link Convert#to(String) Convert#to("objectId")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/">https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/</a>
* @since 2.0.10
*/
public static class ToObjectId extends AbstractAggregationExpression {
private ToObjectId(Object value) {
super(value);
}
/**
* Creates new {@link ToObjectId} using the given value as input.
*
* @param value must not be {@literal null}.
* @return new instance of {@link ToObjectId}.
*/
public static ToObjectId toObjectId(Object value) {
return new ToObjectId(value);
}
@Override
protected String getMongoMethod() {
return "$toObjectId";
}
}
/**
* {@link AggregationExpression} for {@code $toString} that converts a value to {@literal string}. Shorthand for
* {@link Convert#to(String) Convert#to("string")}. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @see <a href=
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toString/">https://docs.mongodb.com/manual/reference/operator/aggregation/toString/</a>
* @since 2.0.10
*/
public static class ToString extends AbstractAggregationExpression {
private ToString(Object value) {
super(value);
}
/**
* Creates new {@link ToString} using the given value as input.
*
* @param value must not be {@literal null}.
* @return new instance of {@link ToString}.
*/
public static ToString toString(Object value) {
return new ToString(value);
}
@Override
protected String getMongoMethod() {
return "$toString";
}
}
}

View File

@@ -22,6 +22,7 @@ import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Gateway to {@literal Date} aggregation operations.
@@ -98,7 +99,7 @@ public class DateOperators {
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
*
* @return new instance of {@link DateFromPartsOperatorFactory}.
* @since 2.1
* @since 2.0.10
*/
public static DateFromString dateFromString(String value) {
return DateFromString.fromString(value);
@@ -178,7 +179,7 @@ public class DateOperators {
* Create a {@link Timezone} for the {@link AggregationExpression} resulting in the Olson Timezone Identifier or UTC
* Offset.
*
* @param value the {@link AggregationExpression} resulting in the timezone.
* @param expression the {@link AggregationExpression} resulting in the timezone.
* @return new instance of {@link Timezone}.
*/
public static Timezone ofExpression(AggregationExpression expression) {
@@ -380,6 +381,17 @@ public class DateOperators {
return applyTimezone(DateToString.dateToString(dateReference()).toString(format), timezone);
}
/**
* Creates new {@link AggregationExpression} that converts a date object to a string according to the server default
* format.
*
* @return new instance of {@link DateToString}.
* @since 2.0.10
*/
public DateToString toStringWithDefaultFormat() {
return applyTimezone(DateToString.dateToString(dateReference()).defaultFormat(), timezone);
}
/**
* Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601 format, ranging from 1
* (for Monday) to 7 (for Sunday).
@@ -1352,6 +1364,11 @@ public class DateOperators {
Assert.notNull(format, "Format must not be null!");
return new DateToString(argumentMap(value, format, Timezone.none()));
}
@Override
public DateToString defaultFormat() {
return new DateToString(argumentMap(value, null, Timezone.none()));
}
};
}
@@ -1392,7 +1409,43 @@ public class DateOperators {
public DateToString withTimezone(Timezone timezone) {
Assert.notNull(timezone, "Timezone must not be null.");
return new DateToString(argumentMap(get("date"), get("format"), timezone));
return new DateToString(append("timezone", timezone));
}
/**
* Optionally specify the value to return when the date is {@literal null} or missing. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param value must not be {@literal null}.
* @return new instance of {@link DateToString}.
* @since 2.0.10
*/
public DateToString onNullReturn(Object value) {
return new DateToString(append("onNull", value));
}
/**
* Optionally specify the field holding the value to return when the date is {@literal null} or missing. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link DateToString}.
* @since 2.0.10
*/
public DateToString onNullReturnValueOf(String fieldReference) {
return onNullReturn(Fields.field(fieldReference));
}
/**
* Optionally specify the expression to evaluate and return when the date is {@literal null} or missing. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link DateToString}.
* @since 2.0.10
*/
public DateToString onNullReturnValueOf(AggregationExpression expression) {
return onNullReturn(expression);
}
@Override
@@ -1400,10 +1453,14 @@ public class DateOperators {
return "$dateToString";
}
private static java.util.Map<String, Object> argumentMap(Object date, String format, Timezone timezone) {
private static java.util.Map<String, Object> argumentMap(Object date, @Nullable String format, Timezone timezone) {
java.util.Map<String, Object> args = new LinkedHashMap<>(2);
if (StringUtils.hasText(format)) {
args.put("format", format);
}
java.util.Map<String, Object> args = new LinkedHashMap<String, Object>(2);
args.put("format", format);
args.put("date", date);
if (!ObjectUtils.nullSafeEquals(timezone, Timezone.none())) {
@@ -1412,6 +1469,25 @@ public class DateOperators {
return args;
}
protected java.util.Map<String, Object> append(String key, Object value) {
java.util.Map<String, Object> clone = new LinkedHashMap<>(argumentMap());
if (value instanceof Timezone) {
if (ObjectUtils.nullSafeEquals(value, Timezone.none())) {
clone.remove("timezone");
} else {
clone.put("timezone", ((Timezone) value).value);
}
} else {
clone.put(key, value);
}
return clone;
}
public interface FormatBuilder {
/**
@@ -1421,6 +1497,16 @@ public class DateOperators {
* @return
*/
DateToString toString(String format);
/**
* Creates new {@link DateToString} using the server default string format ({@code %Y-%m-%dT%H:%M:%S.%LZ}) for
* dates. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link DateToString}.
* @since 2.0.10
*/
DateToString defaultFormat();
}
}
@@ -2270,6 +2356,20 @@ public class DateOperators {
return new DateFromString(appendTimezone(argumentMap(), timezone));
}
/**
* Optionally set the date format to use. If not specified {@code %Y-%m-%dT%H:%M:%S.%LZ} is used.<br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param format must not be {@literal null}.
* @return new instance of {@link DateFromString}.
* @throws IllegalArgumentException if given {@literal format} is {@literal null}.
*/
public DateFromString withFormat(String format) {
Assert.notNull(format, "Format must not be null!");
return new DateFromString(append("format", format));
}
@Override
protected String getMongoMethod() {
return "$dateFromString";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,40 +17,71 @@ package org.springframework.data.mongodb.core.aggregation;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Represents a {@code geoNear} aggregation operation.
* <p>
* We recommend to use the static factory method {@link Aggregation#geoNear(NearQuery, String)} instead of creating
* instances of this class directly.
*
*
* @author Thomas Darimont
* @author Christoph Strobl
* @since 1.3
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/">MongoDB Aggregation Framework:
* $geoNear</a>
*/
public class GeoNearOperation implements AggregationOperation {
private final NearQuery nearQuery;
private final String distanceField;
private final @Nullable String indexKey;
/**
* Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The
* {@code distanceField} defines output field that contains the calculated distance.
*
* @param query must not be {@literal null}.
*
* @param nearQuery must not be {@literal null}.
* @param distanceField must not be {@literal null}.
*/
public GeoNearOperation(NearQuery nearQuery, String distanceField) {
this(nearQuery, distanceField, null);
}
/**
* Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The
* {@code distanceField} defines output field that contains the calculated distance.
*
* @param nearQuery must not be {@literal null}.
* @param distanceField must not be {@literal null}.
* @param indexKey can be {@literal null};
* @since 2.0.10
*/
private GeoNearOperation(NearQuery nearQuery, String distanceField, @Nullable String indexKey) {
Assert.notNull(nearQuery, "NearQuery must not be null.");
Assert.hasLength(distanceField, "Distance field must not be null or empty.");
this.nearQuery = nearQuery;
this.distanceField = distanceField;
this.indexKey = indexKey;
}
/*
/**
* Optionally specify the geospatial index to use via the field to use in the calculation. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param key the geospatial index field to use when calculating the distance.
* @return new instance of {@link GeoNearOperation}.
* @since 2.0.10
*/
public GeoNearOperation useIndex(String key) {
return new GeoNearOperation(nearQuery, distanceField, key);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@@ -60,6 +91,10 @@ public class GeoNearOperation implements AggregationOperation {
Document command = context.getMappedObject(nearQuery.toDocument());
command.put("distanceField", distanceField);
if (StringUtils.hasText(indexKey)) {
command.put("key", indexKey);
}
return new Document("$geoNear", command);
}
}

View File

@@ -103,8 +103,8 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith);
graphLookup.put("connectFromField", connectFrom.getName());
graphLookup.put("connectToField", connectTo.getName());
graphLookup.put("connectFromField", connectFrom.getTarget());
graphLookup.put("connectToField", connectTo.getTarget());
graphLookup.put("as", as.getName());
if (maxDepth != null) {
@@ -112,7 +112,7 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
}
if (depthField != null) {
graphLookup.put("depthField", depthField.getName());
graphLookup.put("depthField", depthField.getTarget());
}
if (restrictSearchWithMatch != null) {

View File

@@ -1204,6 +1204,18 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
return this.operation.and(DateOperators.DateToString.dateOf(getRequiredName()).toString(format));
}
/**
* Generates a {@code $dateToString} expression that takes the date representation of the previously mentioned field
* using the server default format. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return
* @since 2.0.10
*/
public ProjectionOperationBuilder dateAsFormattedString() {
return this.operation.and(DateOperators.DateToString.dateOf(getRequiredName()).defaultFormat());
}
/**
* Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the
* result of the expression.

View File

@@ -350,8 +350,7 @@ public class StringOperators {
* @return
*/
public StrLenBytes length() {
return usesFieldRef() ? StrLenBytes.stringLengthOf(fieldReference)
: StrLenBytes.stringLengthOf(expression);
return usesFieldRef() ? StrLenBytes.stringLengthOf(fieldReference) : StrLenBytes.stringLengthOf(expression);
}
/**
@@ -391,6 +390,132 @@ public class StringOperators {
return usesFieldRef() ? SubstrCP.valueOf(fieldReference) : SubstrCP.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
* from the beginning and end. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link Trim}.
* @since 2.0.10
*/
public Trim trim() {
return createTrim();
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
* character sequence from the beginning and end. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param chars must not be {@literal null}.
* @return new instance of {@link Trim}.
* @since 2.0.10
*/
public Trim trim(String chars) {
return trim().chars(chars);
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
* sequence resulting from the given {@link AggregationExpression} from the beginning and end. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Trim}.
* @since 2.0.10
*/
public Trim trim(AggregationExpression expression) {
return trim().charsOf(expression);
}
private Trim createTrim() {
return usesFieldRef() ? Trim.valueOf(fieldReference) : Trim.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
* from the beginning. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link LTrim}.
* @since 2.0.10
*/
public LTrim ltrim() {
return createLTrim();
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
* character sequence from the beginning. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param chars must not be {@literal null}.
* @return new instance of {@link LTrim}.
* @since 2.0.10
*/
public LTrim ltrim(String chars) {
return ltrim().chars(chars);
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
* sequence resulting from the given {@link AggregationExpression} from the beginning. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link LTrim}.
* @since 2.0.10
*/
public LTrim ltrim(AggregationExpression expression) {
return ltrim().charsOf(expression);
}
private LTrim createLTrim() {
return usesFieldRef() ? LTrim.valueOf(fieldReference) : LTrim.valueOf(expression);
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
* from the end. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @return new instance of {@link RTrim}.
* @since 2.0.10
*/
public RTrim rtrim() {
return createRTrim();
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
* character sequence from the end. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param chars must not be {@literal null}.
* @return new instance of {@link RTrim}.
* @since 2.0.10
*/
public RTrim rtrim(String chars) {
return rtrim().chars(chars);
}
/**
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
* sequence resulting from the given {@link AggregationExpression} from the end. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link RTrim}.
* @since 2.0.10
*/
public RTrim rtrim(AggregationExpression expression) {
return rtrim().charsOf(expression);
}
private RTrim createRTrim() {
return usesFieldRef() ? RTrim.valueOf(fieldReference) : RTrim.valueOf(expression);
}
private boolean usesFieldRef() {
return fieldReference != null;
}
@@ -1072,4 +1197,257 @@ public class StringOperators {
return new SubstrCP(append(Arrays.asList(start, nrOfChars)));
}
}
/**
* {@link AggregationExpression} for {@code $trim} which removes whitespace or the specified characters from the
* beginning and end of a string. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @since 2.0.10
*/
public static class Trim extends AbstractAggregationExpression {
private Trim(Object value) {
super(value);
}
/**
* Creates new {@link Trim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link LTrim}.
*/
public static Trim valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Trim(Collections.singletonMap("input", Fields.field(fieldReference)));
}
/**
* Creates new {@link Trim} using the result of the provided {@link AggregationExpression} as {@literal input}
* value.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Trim}.
*/
public static Trim valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Trim(Collections.singletonMap("input", expression));
}
/**
* Optional specify the character(s) to trim from the beginning.
*
* @param chars must not be {@literal null}.
* @return new instance of {@link Trim}.
*/
public Trim chars(String chars) {
Assert.notNull(chars, "Chars must not be null!");
return new Trim(append("chars", chars));
}
/**
* Optional specify the reference to the {@link Field field} holding the character values to trim from the
* beginning.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Trim}.
*/
public Trim charsOf(String fieldReference) {
return new Trim(append("chars", Fields.field(fieldReference)));
}
/**
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the
* beginning.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Trim}.
*/
public Trim charsOf(AggregationExpression expression) {
return new Trim(append("chars", expression));
}
/**
* Remove whitespace or the specified characters from the beginning of a string.<br />
*
* @return new instance of {@link LTrim}.
*/
public LTrim left() {
return new LTrim(argumentMap());
}
/**
* Remove whitespace or the specified characters from the end of a string.<br />
*
* @return new instance of {@link RTrim}.
*/
public RTrim right() {
return new RTrim(argumentMap());
}
@Override
protected String getMongoMethod() {
return "$trim";
}
}
/**
* {@link AggregationExpression} for {@code $ltrim} which removes whitespace or the specified characters from the
* beginning of a string. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @since 2.0.10
*/
public static class LTrim extends AbstractAggregationExpression {
private LTrim(Object value) {
super(value);
}
/**
* Creates new {@link LTrim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link LTrim}.
*/
public static LTrim valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new LTrim(Collections.singletonMap("input", Fields.field(fieldReference)));
}
/**
* Creates new {@link LTrim} using the result of the provided {@link AggregationExpression} as {@literal input}
* value.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link LTrim}.
*/
public static LTrim valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new LTrim(Collections.singletonMap("input", expression));
}
/**
* Optional specify the character(s) to trim from the beginning.
*
* @param chars must not be {@literal null}.
* @return new instance of {@link LTrim}.
*/
public LTrim chars(String chars) {
Assert.notNull(chars, "Chars must not be null!");
return new LTrim(append("chars", chars));
}
/**
* Optional specify the reference to the {@link Field field} holding the character values to trim from the
* beginning.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link LTrim}.
*/
public LTrim charsOf(String fieldReference) {
return new LTrim(append("chars", Fields.field(fieldReference)));
}
/**
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the
* beginning.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link LTrim}.
*/
public LTrim charsOf(AggregationExpression expression) {
return new LTrim(append("chars", expression));
}
@Override
protected String getMongoMethod() {
return "$ltrim";
}
}
/**
* {@link AggregationExpression} for {@code $rtrim} which removes whitespace or the specified characters from the end
* of a string. <br />
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
*
* @author Christoph Strobl
* @since 2.0.10
*/
public static class RTrim extends AbstractAggregationExpression {
private RTrim(Object value) {
super(value);
}
/**
* Creates new {@link RTrim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link RTrim}.
*/
public static RTrim valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new RTrim(Collections.singletonMap("input", Fields.field(fieldReference)));
}
/**
* Creates new {@link RTrim} using the result of the provided {@link AggregationExpression} as {@literal input}
* value.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link RTrim}.
*/
public static RTrim valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new RTrim(Collections.singletonMap("input", expression));
}
/**
* Optional specify the character(s) to trim from the end.
*
* @param chars must not be {@literal null}.
* @return new instance of {@link RTrim}.
*/
public RTrim chars(String chars) {
Assert.notNull(chars, "Chars must not be null!");
return new RTrim(append("chars", chars));
}
/**
* Optional specify the reference to the {@link Field field} holding the character values to trim from the end.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link RTrim}.
*/
public RTrim charsOf(String fieldReference) {
return new RTrim(append("chars", Fields.field(fieldReference)));
}
/**
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the end.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link RTrim}.
*/
public RTrim charsOf(AggregationExpression expression) {
return new RTrim(append("chars", expression));
}
@Override
protected String getMongoMethod() {
return "$rtrim";
}
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.mongodb.core.convert;
import lombok.Getter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
@@ -40,7 +42,7 @@ import com.mongodb.DBObject;
*/
class DocumentAccessor {
private final Bson document;
private final @Getter Bson document;
/**
* Creates a new {@link DocumentAccessor} for the given {@link Document}.
@@ -137,15 +139,21 @@ class DocumentAccessor {
String fieldName = property.getFieldName();
if (this.document instanceof Document) {
if (((Document) this.document).containsKey(fieldName)) {
return true;
}
} else if (this.document instanceof DBObject) {
if (((DBObject) this.document).containsField(fieldName)) {
return true;
}
}
if (!fieldName.contains(".")) {
if (this.document instanceof Document) {
return ((Document) this.document).containsKey(fieldName);
}
if (this.document instanceof DBObject) {
return ((DBObject) this.document).containsField(fieldName);
}
return false;
}
String[] parts = fieldName.split("\\.");

View File

@@ -15,17 +15,8 @@
*/
package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import org.bson.Document;
import org.bson.conversions.Bson;
@@ -42,6 +33,7 @@ import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@@ -58,6 +50,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -215,6 +208,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (conversions.hasCustomReadTarget(bson.getClass(), rawType)) {
return conversionService.convert(bson, rawType);
} else if (bson instanceof DBObject && conversions.hasCustomReadTarget(Document.class, rawType)) {
return conversionService.convert(new Document(BsonUtils.asMap(bson)), rawType);
}
if (DBObject.class.isAssignableFrom(rawType)) {
@@ -250,11 +245,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
throw new MappingException(String.format(INVALID_TYPE_TO_READ, target, typeToUse.getType()));
}
return read((MongoPersistentEntity<S>) mappingContext.getRequiredPersistentEntity(typeToUse), target, path);
return read((MongoPersistentEntity<S>) entity, target, path);
}
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
Bson source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {
DocumentAccessor source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, path);
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
@@ -267,8 +262,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final Document bson, final ObjectPath path) {
DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
PreferredConstructor<S, MongoPersistentProperty> constructor = entity.getPersistenceConstructor();
ParameterValueProvider<MongoPersistentProperty> provider = constructor != null && constructor.hasParameters() //
? getParameterProvider(entity, documentAccessor, evaluator, path) //
: NoOpParameterValueProvider.INSTANCE;
ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, bson, evaluator, path);
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
S instance = instantiator.createInstance(entity, provider);
@@ -276,7 +277,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
conversionService);
MongoPersistentProperty idProperty = entity.getIdProperty();
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
// make sure id property is set before all other properties
Object idValue = null;
@@ -292,9 +292,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
currentPath);
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(bson, currentPath, evaluator,
MappingMongoConverter.this);
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider, callback);
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider, currentPath, evaluator);
return instance;
}
@@ -310,10 +308,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
@Nullable MongoPersistentProperty idProperty, DocumentAccessor documentAccessor,
MongoDbPropertyValueProvider valueProvider, DbRefResolverCallback callback) {
MongoDbPropertyValueProvider valueProvider, ObjectPath currentPath, SpELExpressionEvaluator evaluator) {
DbRefResolverCallback callback = null;
for (MongoPersistentProperty prop : entity) {
if (callback == null) {
callback = getDbRefResolverCallback(documentAccessor, currentPath, evaluator);
}
if (prop.isAssociation() && !entity.isConstructorArgument(prop)) {
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
continue;
@@ -336,6 +340,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
}
private DbRefResolverCallback getDbRefResolverCallback(DocumentAccessor documentAccessor, ObjectPath currentPath,
SpELExpressionEvaluator evaluator) {
return new DefaultDbRefResolverCallback(documentAccessor.getDocument(), currentPath, evaluator,
MappingMongoConverter.this);
}
private void readAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor accessor,
DocumentAccessor documentAccessor, DbRefProxyHandler handler, DbRefResolverCallback callback) {
@@ -393,12 +404,23 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
removeFromMap(bson, "_id");
}
boolean handledByCustomConverter = conversions.hasCustomWriteTarget(entityType, Document.class);
if (!handledByCustomConverter && !(bson instanceof Collection)) {
if (requiresTypeHint(entityType)) {
typeMapper.writeType(type, bson);
}
}
/**
* Check if a given type requires a type hint (aka {@literal _class} attribute) when writing to the document.
*
* @param type must not be {@literal null}.
* @return {@literal true} if not a simple type, {@link Collection} or type with custom write target.
*/
private boolean requiresTypeHint(Class<?> type) {
return !conversions.isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type)
&& !conversions.hasCustomWriteTarget(type, Document.class);
}
/**
* Internal write conversion method which should be used for nested invocations.
*
@@ -556,7 +578,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return;
}
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass())
MongoPersistentEntity<?> entity = isSubTypeOf(obj.getClass(), prop.getType())
? mappingContext.getRequiredPersistentEntity(obj.getClass())
: mappingContext.getRequiredPersistentEntity(type);
@@ -568,10 +590,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
accessor.put(prop, document);
}
private boolean isSubtype(Class<?> left, Class<?> right) {
return left.isAssignableFrom(right) && !left.equals(right);
}
/**
* Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element
@@ -661,11 +679,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param sink the {@link Collection} to write to.
* @return
*/
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type, Collection<?> sink) {
@SuppressWarnings("unchecked")
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
Collection<?> sink) {
TypeInformation<?> componentType = null;
List<Object> collection = sink instanceof List ? (List) sink : new ArrayList<>(sink);
List<Object> collection = sink instanceof List ? (List<Object>) sink : new ArrayList<>(sink);
if (type != null) {
componentType = type.getComponentType();
@@ -870,7 +890,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
*/
@Nullable
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
return value;
@@ -946,7 +966,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Assert.notNull(path, "Object path must not be null!");
Class<?> collectionType = targetType.getType();
collectionType = Collection.class.isAssignableFrom(collectionType) //
collectionType = isSubTypeOf(collectionType, Collection.class) //
? collectionType //
: List.class;
@@ -980,13 +1000,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
items.add(read(componentType, (BasicDBObject) element, path));
} else {
if (element instanceof Collection) {
if (!Object.class.equals(rawComponentType) && element instanceof Collection) {
if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) {
throw new MappingException(
String.format(INCOMPATIBLE_TYPES, element, element.getClass(), rawComponentType, path));
}
}
if (element instanceof List) {
items.add(readCollectionOrArray(componentType, (Collection<Object>) element, path));
} else {
@@ -1521,6 +1540,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return true;
}
/**
* Returns whether the given type is a sub type of the given reference, i.e. assignable but not the exact same type.
*
* @param type must not be {@literal null}.
* @param reference must not be {@literal null}.
* @return
*/
private static boolean isSubTypeOf(Class<?> type, Class<?> reference) {
return !type.equals(reference) && reference.isAssignableFrom(type);
}
/**
* Marker class used to indicate we have a non root document object here that might be used within an update - so we
* need to preserve type hints for potential nested elements but need to remove it on top level.
@@ -1531,4 +1561,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
static class NestedDocument {
}
enum NoOpParameterValueProvider implements ParameterValueProvider<MongoPersistentProperty> {
INSTANCE;
@Override
public <T> T getParameterValue(Parameter<T, MongoPersistentProperty> parameter) {
return null;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.core.convert;
import lombok.Value;
import java.util.ArrayList;
import java.util.List;
@@ -45,26 +43,33 @@ class ObjectPath {
static final ObjectPath ROOT = new ObjectPath();
private final ObjectPathItem[] items;
private final @Nullable ObjectPath parent;
private final @Nullable Object object;
private final @Nullable Object idValue;
private final String collection;
private ObjectPath() {
this.items = new ObjectPathItem[0];
this.parent = null;
this.object = null;
this.idValue = null;
this.collection = "";
}
/**
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided
* {@link ObjectPathItem} to it.
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} and adding the provided path values.
*
* @param parent must not be {@literal null}.
* @param item
* @param collection
* @param idValue
* @param collection
*/
private ObjectPath(ObjectPath parent, ObjectPath.ObjectPathItem item) {
private ObjectPath(ObjectPath parent, Object object, @Nullable Object idValue, String collection) {
ObjectPathItem[] items = new ObjectPathItem[parent.items.length + 1];
System.arraycopy(parent.items, 0, items, 0, parent.items.length);
items[parent.items.length] = item;
this.items = items;
this.parent = parent;
this.object = object;
this.idValue = idValue;
this.collection = collection;
}
/**
@@ -80,8 +85,7 @@ class ObjectPath {
Assert.notNull(object, "Object must not be null!");
Assert.notNull(entity, "MongoPersistentEntity must not be null!");
ObjectPathItem item = new ObjectPathItem(object, id, entity.getCollection());
return new ObjectPath(this, item);
return new ObjectPath(this, object, id, entity.getCollection());
}
/**
@@ -100,15 +104,15 @@ class ObjectPath {
Assert.notNull(id, "Id must not be null!");
Assert.hasText(collection, "Collection name must not be null!");
for (ObjectPathItem item : items) {
for (ObjectPath current = this; current != null; current = current.parent) {
Object object = item.getObject();
Object object = current.getObject();
if (object == null || item.getIdValue() == null) {
if (object == null || current.getIdValue() == null) {
continue;
}
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())) {
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())) {
return object;
}
}
@@ -133,15 +137,15 @@ class ObjectPath {
Assert.hasText(collection, "Collection name must not be null!");
Assert.notNull(type, "Type must not be null!");
for (ObjectPathItem item : items) {
for (ObjectPath current = this; current != null; current = current.parent) {
Object object = item.getObject();
Object object = current.getObject();
if (object == null || item.getIdValue() == null) {
if (object == null || current.getIdValue() == null) {
continue;
}
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())
&& ClassUtils.isAssignable(type, object.getClass())) {
return type.cast(object);
}
@@ -157,7 +161,21 @@ class ObjectPath {
*/
@Nullable
Object getCurrentObject() {
return items.length == 0 ? null : items[items.length - 1].getObject();
return getObject();
}
@Nullable
private Object getObject() {
return object;
}
@Nullable
private Object getIdValue() {
return idValue;
}
private String getCollection() {
return collection;
}
/*
@@ -167,31 +185,16 @@ class ObjectPath {
@Override
public String toString() {
if (items.length == 0) {
if (parent == null) {
return "[empty]";
}
List<String> strings = new ArrayList<>(items.length);
List<String> strings = new ArrayList<>();
for (ObjectPathItem item : items) {
strings.add(ObjectUtils.nullSafeToString(item.object));
for (ObjectPath current = this; current != null; current = current.parent) {
strings.add(ObjectUtils.nullSafeToString(current.getObject()));
}
return StringUtils.collectionToDelimitedString(strings, " -> ");
}
/**
* An item in an {@link ObjectPath}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
private static class ObjectPathItem {
Object object;
@Nullable Object idValue;
String collection;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2017 the original author or authors.
* Copyright 2011-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,11 +24,14 @@ import org.springframework.lang.Nullable;
* {@link MongoPersistentProperty} caching access to {@link #isIdProperty()} and {@link #getFieldName()}.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty {
private @Nullable Boolean isIdProperty;
private @Nullable Boolean isAssociation;
private @Nullable boolean dbRefResolved;
private @Nullable DBRef dbref;
private @Nullable String fieldName;
private @Nullable Boolean usePropertyAccess;
private @Nullable Boolean isTransient;
@@ -36,8 +39,7 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
/**
* Creates a new {@link CachingMongoPersistentProperty}.
*
* @param field
* @param propertyDescriptor
* @param property
* @param owner
* @param simpleTypeHolder
* @param fieldNamingStrategy
@@ -67,9 +69,11 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
*/
@Override
public boolean isAssociation() {
if (this.isAssociation == null) {
this.isAssociation = super.isAssociation();
}
return this.isAssociation;
}
@@ -114,4 +118,28 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
return this.isTransient;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#isDbReference()
*/
@Override
public boolean isDbReference() {
return getDBRef() != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#getDBRef()
*/
@Override
public DBRef getDBRef() {
if (!dbRefResolved) {
this.dbref = super.getDBRef();
this.dbRefResolved = true;
}
return this.dbref;
}
}

View File

@@ -24,6 +24,8 @@ import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.lang.Nullable;
import com.mongodb.MapReduceCommand;
import com.mongodb.MapReduceCommand.OutputType;
import com.mongodb.client.model.MapReduceAction;
/**
* @author Mark Pollack
@@ -295,6 +297,37 @@ public class MapReduceOptions {
return collation;
}
/**
* Return the {@link MapReduceAction} derived from {@link com.mongodb.MapReduceCommand.OutputType}.
*
* @return the mapped action or {@literal null} if the action maps to inline output.
* @since 2.0.10
*/
@Nullable
public MapReduceAction getMapReduceAction() {
switch (outputType) {
case MERGE:
return MapReduceAction.MERGE;
case REDUCE:
return MapReduceAction.REDUCE;
case REPLACE:
return MapReduceAction.REPLACE;
case INLINE:
return null;
default:
throw new IllegalStateException(String.format("Unknown output type %s for map reduce command.", outputType));
}
}
/**
* @return {@literal true} if {@link OutputType#INLINE} is used.
* @since 2.0.10
*/
public boolean usesInlineOutput() {
return OutputType.INLINE.equals(outputType);
}
public Document getOptionsObject() {
Document cmd = new Document();
@@ -328,7 +361,7 @@ public class MapReduceOptions {
Document out = new Document();
switch (outputType) {
switch (getOutputType()) {
case INLINE:
out.put("inline", 1);
break;

View File

@@ -890,18 +890,14 @@ public class Update {
/**
* Forces values to be added at the given {@literal position}.
*
* @param position needs to be greater than or equal to zero.
* @param position the position offset. As of MongoDB 3.6 use a negative value to indicate starting from the end,
* counting (but not including) the last element of the array.
* @return never {@literal null}.
* @since 1.7
*/
public PushOperatorBuilder atPosition(int position) {
if (position < 0) {
throw new IllegalArgumentException("Position must be greater than or equal to zero.");
}
this.modifiers.addModifier(new PositionModifier(position));
return this;
}

View File

@@ -44,13 +44,14 @@ import com.mongodb.client.gridfs.model.GridFSUploadOptions;
/**
* {@link GridFsOperations} implementation to store content into MongoDB GridFS.
*
*
* @author Oliver Gierke
* @author Philipp Schneider
* @author Thomas Darimont
* @author Martin Baumgartner
* @author Christoph Strobl
* @author Mark Paluch
* @author Niklas Helge Hanft
*/
public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver {
@@ -62,7 +63,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
/**
* Creates a new {@link GridFsTemplate} using the given {@link MongoDbFactory} and {@link MongoConverter}.
*
*
* @param dbFactory must not be {@literal null}.
* @param converter must not be {@literal null}.
*/
@@ -72,7 +73,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
/**
* Creates a new {@link GridFsTemplate} using the given {@link MongoDbFactory} and {@link MongoConverter}.
*
*
* @param dbFactory must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param bucket
@@ -228,7 +229,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
public GridFsResource getResource(String location) {
GridFSFile file = findOne(query(whereFilename().is(location)));
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(location)) : null;
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId())) : null;
}
/*
@@ -246,13 +247,13 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
if (path.isPattern()) {
GridFSFindIterable files = find(query(whereFilename().regex(path.toRegex())));
List<GridFsResource> resources = new ArrayList<GridFsResource>();
List<GridFsResource> resources = new ArrayList<>();
for (GridFSFile file : files) {
resources.add(new GridFsResource(file, getGridFs().openDownloadStream(file.getFilename())));
resources.add(new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId())));
}
return resources.toArray(new GridFsResource[resources.size()]);
return resources.toArray(new GridFsResource[0]);
}
return new GridFsResource[] { getResource(locationPattern) };

View File

@@ -30,6 +30,7 @@ import org.springframework.data.repository.query.QueryByExampleExecutor;
* @author Christoph Strobl
* @author Thomas Darimont
* @author Mark Paluch
* @author Khaled Baklouti
*/
@NoRepositoryBean
public interface MongoRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
@@ -39,7 +40,7 @@ public interface MongoRepository<T, ID> extends PagingAndSortingRepository<T, ID
* @see org.springframework.data.repository.CrudRepository#saveAll(java.lang.Iterable)
*/
@Override
<S extends T> List<S> saveAll(Iterable<S> entites);
<S extends T> List<S> saveAll(Iterable<S> entities);
/*
* (non-Javadoc)

View File

@@ -35,7 +35,6 @@ import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecu
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
/**
@@ -150,6 +149,8 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
} else if (isCountQuery()) {
return (q, t, c) -> operation.matching(q).count();
} else if (isExistsQuery()) {
return (q, t, c) -> operation.matching(q).exists();
} else {
return (q, t, c) -> {
@@ -204,6 +205,14 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
*/
protected abstract boolean isCountQuery();
/**
* Returns whether the query should get an exists projection applied.
*
* @return
* @since 2.0.9
*/
protected abstract boolean isExistsQuery();
/**
* Return weather the query should delete matching documents.
*

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.repository.query;
import lombok.experimental.UtilityClass;
/**
* Utility class containing methods to interact with boolean values.
*
* @author Mark Paluch
* @since 2.0.9
*/
@UtilityClass
class BooleanUtil {
/**
* Count the number of {@literal true} values.
*
* @param values
* @return the number of values that are {@literal true}.
*/
static int countBooleanTrueValues(boolean... values) {
int count = 0;
for (boolean value : values) {
if (value) {
count++;
}
}
return count;
}
}

View File

@@ -16,16 +16,19 @@
package org.springframework.data.mongodb.repository.query;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.UtilityClass;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -206,6 +209,7 @@ class ExpressionEvaluatingParameterBinder {
* @param binding must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
private String getParameterValueForBinding(MongoParameterAccessor accessor, MongoParameters parameters,
ParameterBinding binding) {
@@ -222,43 +226,7 @@ class ExpressionEvaluatingParameterBinder {
return binding.isExpression() ? JSON.serialize(value) : QuotedString.unquote(JSON.serialize(value));
}
if (value instanceof byte[]) {
if (binding.isQuoted()) {
return DatatypeConverter.printBase64Binary((byte[]) value);
}
return encode(new Binary((byte[]) value), BinaryCodec::new);
}
if (value instanceof UUID) {
if (binding.isQuoted()) {
return value.toString();
}
return encode((UUID) value, UuidCodec::new);
}
return JSON.serialize(value);
}
private <T> String encode(T value, Supplier<Codec<T>> defaultCodec) {
Codec<T> codec;
try {
codec = codecRegistry.get((Class<T>) value.getClass());
} catch (CodecConfigurationException exception) {
codec = defaultCodec.get();
}
StringWriter writer = new StringWriter();
codec.encode(new JsonWriter(writer), value, null);
writer.flush();
return writer.toString();
return EncodableValue.create(value).encode(codecRegistry, binding.isQuoted());
}
/**
@@ -480,4 +448,230 @@ class ExpressionEvaluatingParameterBinder {
return quoted.substring(1, quoted.length() - 1);
}
}
/**
* Value object encapsulating a bindable value, that can be encoded to be represented as JSON (BSON).
*
* @author Mark Paluch
*/
abstract static class EncodableValue {
/**
* Obtain a {@link EncodableValue} given {@code value}.
*
* @param value the value to encode, may be {@literal null}.
* @return the {@link EncodableValue} for {@code value}.
*/
@SuppressWarnings("unchecked")
public static EncodableValue create(@Nullable Object value) {
if (value instanceof byte[]) {
return new BinaryValue((byte[]) value);
}
if (value instanceof UUID) {
return new UuidValue((UUID) value);
}
if (value instanceof Collection) {
Collection<?> collection = (Collection<?>) value;
Class<?> commonElement = CollectionUtils.findCommonElementType(collection);
if (commonElement != null) {
if (UUID.class.isAssignableFrom(commonElement)) {
return new UuidCollection((Collection<UUID>) value);
}
if (byte[].class.isAssignableFrom(commonElement)) {
return new BinaryCollectionValue((Collection<byte[]>) value);
}
}
}
return new ObjectValue(value);
}
/**
* Encode the encapsulated value.
*
* @param provider
* @param quoted
* @return
*/
public abstract String encode(CodecRegistry codecRegistry, boolean quoted);
/**
* Encode a {@code value} to JSON.
*
* @param provider
* @param value
* @param defaultCodec
* @param <V>
* @return
*/
protected <V> String encode(CodecRegistry codecRegistry, V value, Supplier<Codec<V>> defaultCodec) {
StringWriter writer = new StringWriter();
doEncode(codecRegistry, writer, value, defaultCodec);
return writer.toString();
}
/**
* Encode a {@link Collection} to JSON and potentially apply a {@link Function mapping function} before encoding.
*
* @param provider
* @param value
* @param mappingFunction
* @param defaultCodec
* @param <I> Input value type.
* @param <V> Target type.
* @return
*/
protected <I, V> String encodeCollection(CodecRegistry codecRegistry, Iterable<I> value,
Function<I, V> mappingFunction, Supplier<Codec<V>> defaultCodec) {
StringWriter writer = new StringWriter();
writer.append("[");
value.forEach(it -> {
if (writer.getBuffer().length() > 1) {
writer.append(", ");
}
doEncode(codecRegistry, writer, mappingFunction.apply(it), defaultCodec);
});
writer.append("]");
writer.flush();
return writer.toString();
}
@SuppressWarnings("unchecked")
private <V> void doEncode(CodecRegistry codecRegistry, StringWriter writer, V value,
Supplier<Codec<V>> defaultCodec) {
Codec<V> codec = getCodec(codecRegistry, (Class<V>) value.getClass(), defaultCodec);
JsonWriter jsonWriter = new JsonWriter(writer);
codec.encode(jsonWriter, value, null);
jsonWriter.flush();
}
private <T> Codec<T> getCodec(CodecRegistry codecRegistry, Class<T> type, Supplier<Codec<T>> defaultCodec) {
try {
return codecRegistry.get(type);
} catch (CodecConfigurationException exception) {
return defaultCodec.get();
}
}
}
/**
* {@link EncodableValue} for {@code byte[]} to render to {@literal $binary}.
*/
@RequiredArgsConstructor
static class BinaryValue extends EncodableValue {
private final byte[] value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
if (quoted) {
return DatatypeConverter.printBase64Binary(this.value);
}
return encode(codecRegistry, new Binary(this.value), BinaryCodec::new);
}
}
/**
* {@link EncodableValue} for {@link Collection} containing only {@code byte[]} items to render to a BSON list
* containing {@literal $binary}.
*/
@RequiredArgsConstructor
static class BinaryCollectionValue extends EncodableValue {
private final Collection<byte[]> value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
return encodeCollection(codecRegistry, this.value, Binary::new, BinaryCodec::new);
}
}
/**
* {@link EncodableValue} for {@link UUID} to render to {@literal $binary}.
*/
@RequiredArgsConstructor
static class UuidValue extends EncodableValue {
private final UUID value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
if (quoted) {
return this.value.toString();
}
return encode(codecRegistry, this.value, UuidCodec::new);
}
}
/**
* {@link EncodableValue} for {@link Collection} containing only {@link UUID} items to render to a BSON list
* containing {@literal $binary}.
*/
@RequiredArgsConstructor
static class UuidCollection extends EncodableValue {
private final Collection<UUID> value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
return encodeCollection(codecRegistry, this.value, Function.identity(), UuidCodec::new);
}
}
/**
* Fallback-{@link EncodableValue} for {@link Object}-typed values.
*/
@RequiredArgsConstructor
static class ObjectValue extends EncodableValue {
private final @Nullable Object value;
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
*/
@Override
public String encode(CodecRegistry codecRegistry, boolean quoted) {
return JSON.serialize(this.value);
}
}
}

View File

@@ -134,6 +134,15 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
return tree.isCountProjection();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery()
*/
@Override
protected boolean isExistsQuery() {
return tree.isExistsProjection();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()

View File

@@ -40,13 +40,14 @@ import org.springframework.util.Assert;
*/
public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!";
private static final Logger LOG = LoggerFactory.getLogger(ReactiveStringBasedMongoQuery.class);
private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE;
private final String query;
private final String fieldSpec;
private final boolean isCountQuery;
private final boolean isExistsQuery;
private final boolean isDeleteQuery;
private final List<ParameterBinding> queryParameterBindings;
private final List<ParameterBinding> fieldSpecParameterBindings;
@@ -92,11 +93,23 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(
method.getFieldSpecification(), this.fieldSpecParameterBindings);
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
if (method.hasAnnotatedQuery()) {
if (isCountQuery && isDeleteQuery) {
throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method));
org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation();
this.isCountQuery = queryAnnotation.count();
this.isExistsQuery = queryAnnotation.exists();
this.isDeleteQuery = queryAnnotation.delete();
if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) {
throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method));
}
} else {
this.isCountQuery = false;
this.isExistsQuery = false;
this.isDeleteQuery = false;
}
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
@@ -132,6 +145,15 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
return isCountQuery;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery()
*/
@Override
protected boolean isExistsQuery() {
return isExistsQuery;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
@@ -150,4 +172,9 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
return false;
}
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
boolean isDeleteQuery) {
return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
}
}

View File

@@ -20,7 +20,9 @@ import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.bson.BSON;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,6 +39,7 @@ import org.springframework.util.StringUtils;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
import com.mongodb.util.JSON;
import com.mongodb.util.JSONCallback;
/**
* Query to use a plain JSON String to create the {@link Query} to actually execute.
@@ -169,11 +172,6 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
return this.isDeleteQuery;
}
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
boolean isDeleteQuery) {
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting()
@@ -183,18 +181,9 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
return false;
}
private static int countBooleanValues(boolean... values) {
int count = 0;
for (boolean value : values) {
if (value) {
count++;
}
}
return count;
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
boolean isDeleteQuery) {
return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
}
/**
@@ -238,7 +227,8 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
String transformedInput = transformQueryAndCollectExpressionParametersIntoBindings(input, bindings);
String parseableInput = makeParameterReferencesParseable(transformedInput);
collectParameterReferencesIntoBindings(bindings, JSON.parse(parseableInput));
collectParameterReferencesIntoBindings(bindings,
JSON.parse(parseableInput, new LenientPatternDecodingCallback()));
return transformedInput;
}
@@ -373,6 +363,43 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
}
}
/**
* {@link JSONCallback} with lenient handling for {@link PatternSyntaxException} falling back to a placeholder
* {@link Pattern} for intermediate query document rendering.
*/
private static class LenientPatternDecodingCallback extends JSONCallback {
private static final Pattern EMPTY_MARKER = Pattern.compile("__Spring_Data_MongoDB_Bind_Marker__");
/*
* (non-Javadoc)
* @see com.mongodb.util.JSONCallback#objectDone()
*/
@Override
public Object objectDone() {
return exceptionSwallowingStackReducingObjectDone();
}
private Object exceptionSwallowingStackReducingObjectDone/*CauseWeJustNeedTheStructureNotTheActualValue*/() {
Object value;
try {
return super.objectDone();
} catch (PatternSyntaxException e) {
value = EMPTY_MARKER;
}
if (!isStackEmpty()) {
_put(curName(), value);
} else {
value = !BSON.hasDecodeHooks() ? value : BSON.applyDecodingHooks(value);
setRoot(value);
}
return value;
}
}
/**
* A generic parameter binding with name or position information.
*

View File

@@ -20,7 +20,7 @@ import org.springframework.util.ClassUtils;
/**
* {@link MongoClientVersion} holds information about the used mongo-java client and is used to distinguish between
* different versions.
*
*
* @author Christoph Strobl
* @since 1.7
*/
@@ -32,6 +32,9 @@ public class MongoClientVersion {
private static final boolean IS_MONGO_34 = ClassUtils.isPresent("org.bson.types.Decimal128",
MongoClientVersion.class.getClassLoader());
private static final boolean IS_MONGO_38 = ClassUtils.isPresent("com.mongodb.TransactionOptions",
MongoClientVersion.class.getClassLoader());
private static final boolean IS_ASYNC_CLIENT = ClassUtils.isPresent("com.mongodb.async.client.MongoClient",
MongoClientVersion.class.getClassLoader());
@@ -51,9 +54,17 @@ public class MongoClientVersion {
}
/**
* @return {lliteral true} if MongoDB Java driver is on classpath.
* @return {@literal true} if MongoDB Java driver is on classpath.
*/
public static boolean isAsyncClient() {
return IS_ASYNC_CLIENT;
}
/**
* @return {@literal true} if MongoDB Java driver version 3.8 or later is on classpath.
* @since 2.0.10
*/
public static boolean isMongo38Driver() {
return IS_MONGO_38;
}
}

View File

@@ -38,7 +38,7 @@ public final class MongoDbErrorCodes {
static {
dataAccessResourceFailureCodes = new HashMap<Integer, String>(10);
dataAccessResourceFailureCodes = new HashMap<>(12, 1f);
dataAccessResourceFailureCodes.put(6, "HostUnreachable");
dataAccessResourceFailureCodes.put(7, "HostNotFound");
dataAccessResourceFailureCodes.put(89, "NetworkTimeout");
@@ -52,7 +52,7 @@ public final class MongoDbErrorCodes {
dataAccessResourceFailureCodes.put(13441, "BadOffsetInFile");
dataAccessResourceFailureCodes.put(13640, "DataFileHeaderCorrupt");
dataIntegrityViolationCodes = new HashMap<Integer, String>(6);
dataIntegrityViolationCodes = new HashMap<>(6, 1f);
dataIntegrityViolationCodes.put(67, "CannotCreateIndex");
dataIntegrityViolationCodes.put(68, "IndexAlreadyExists");
dataIntegrityViolationCodes.put(85, "IndexOptionsConflict");
@@ -60,13 +60,13 @@ public final class MongoDbErrorCodes {
dataIntegrityViolationCodes.put(112, "WriteConflict");
dataIntegrityViolationCodes.put(117, "ConflictingOperationInProgress");
duplicateKeyCodes = new HashMap<Integer, String>(3);
duplicateKeyCodes = new HashMap<>(4, 1f);
duplicateKeyCodes.put(3, "OBSOLETE_DuplicateKey");
duplicateKeyCodes.put(84, "DuplicateKeyValue");
duplicateKeyCodes.put(11000, "DuplicateKey");
duplicateKeyCodes.put(11001, "DuplicateKey");
invalidDataAccessApiUsageExeption = new HashMap<Integer, String>();
invalidDataAccessApiUsageExeption = new HashMap<>(31, 1f);
invalidDataAccessApiUsageExeption.put(5, "GraphContainsCycle");
invalidDataAccessApiUsageExeption.put(9, "FailedToParse");
invalidDataAccessApiUsageExeption.put(14, "TypeMismatch");
@@ -80,8 +80,7 @@ public final class MongoDbErrorCodes {
invalidDataAccessApiUsageExeption.put(30, "InvalidPath");
invalidDataAccessApiUsageExeption.put(40, "ConflictingUpdateOperators");
invalidDataAccessApiUsageExeption.put(45, "UserDataInconsistent");
invalidDataAccessApiUsageExeption.put(30, "DollarPrefixedFieldName");
invalidDataAccessApiUsageExeption.put(52, "InvalidPath");
invalidDataAccessApiUsageExeption.put(52, "DollarPrefixedFieldName");
invalidDataAccessApiUsageExeption.put(53, "InvalidIdField");
invalidDataAccessApiUsageExeption.put(54, "NotSingleValueField");
invalidDataAccessApiUsageExeption.put(55, "InvalidDBRef");
@@ -99,17 +98,17 @@ public final class MongoDbErrorCodes {
invalidDataAccessApiUsageExeption.put(17280, "KeyTooLong");
invalidDataAccessApiUsageExeption.put(13334, "ShardKeyTooBig");
permissionDeniedCodes = new HashMap<Integer, String>();
permissionDeniedCodes = new HashMap<>(8, 1f);
permissionDeniedCodes.put(11, "UserNotFound");
permissionDeniedCodes.put(18, "AuthenticationFailed");
permissionDeniedCodes.put(31, "RoleNotFound");
permissionDeniedCodes.put(32, "RolesNotRelated");
permissionDeniedCodes.put(33, "PrvilegeNotFound");
permissionDeniedCodes.put(33, "PrivilegeNotFound");
permissionDeniedCodes.put(15847, "CannotAuthenticate");
permissionDeniedCodes.put(16704, "CannotAuthenticateToAdminDB");
permissionDeniedCodes.put(16705, "CannotAuthenticateToAdminDB");
errorCodes = new HashMap<Integer, String>();
errorCodes = new HashMap<>();
errorCodes.putAll(dataAccessResourceFailureCodes);
errorCodes.putAll(dataIntegrityViolationCodes);
errorCodes.putAll(duplicateKeyCodes);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import kotlin.reflect.KClass
* @since 2.0
*/
fun <T : Any> ExecutableFindOperation.query(entityClass: KClass<T>): ExecutableFindOperation.ExecutableFind<T> =
query(entityClass.java)
query(entityClass.java)
/**
* Extension for [ExecutableFindOperation.query] leveraging reified type parameters.
@@ -35,8 +35,7 @@ fun <T : Any> ExecutableFindOperation.query(entityClass: KClass<T>): ExecutableF
* @since 2.0
*/
inline fun <reified T : Any> ExecutableFindOperation.query(): ExecutableFindOperation.ExecutableFind<T> =
query(T::class.java)
query(T::class.java)
/**
* Extension for [ExecutableFindOperation.FindWithProjection. as] providing a [KClass] based variant.
@@ -45,8 +44,8 @@ inline fun <reified T : Any> ExecutableFindOperation.query(): ExecutableFindOper
* @author Mark Paluch
* @since 2.0
*/
fun <T : Any> ExecutableFindOperation.FindWithProjection<T>.asType(resultType: KClass<T>): ExecutableFindOperation.FindWithQuery<T> =
`as`(resultType.java)
fun <T : Any> ExecutableFindOperation.FindWithProjection<*>.asType(resultType: KClass<T>): ExecutableFindOperation.FindWithQuery<T> =
`as`(resultType.java)
/**
* Extension for [ExecutableFindOperation.FindWithProjection. as] leveraging reified type parameters.
@@ -55,7 +54,5 @@ fun <T : Any> ExecutableFindOperation.FindWithProjection<T>.asType(resultType: K
* @author Mark Paluch
* @since 2.0
*/
inline fun <reified T : Any> ExecutableFindOperation.FindWithProjection<T>.asType(): ExecutableFindOperation.FindWithQuery<T> =
`as`(T::class.java)
inline fun <reified T : Any> ExecutableFindOperation.FindWithProjection<*>.asType(): ExecutableFindOperation.FindWithQuery<T> =
`as`(T::class.java)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ inline fun <reified T : Any> ReactiveFindOperation.query(): ReactiveFindOperatio
* @author Mark Paluch
* @since 2.0
*/
fun <T : Any> ReactiveFindOperation.FindWithProjection<T>.asType(resultType: KClass<T>): ReactiveFindOperation.FindWithQuery<T> =
fun <T : Any> ReactiveFindOperation.FindWithProjection<*>.asType(resultType: KClass<T>): ReactiveFindOperation.FindWithQuery<T> =
`as`(resultType.java)
/**
@@ -50,7 +50,7 @@ fun <T : Any> ReactiveFindOperation.FindWithProjection<T>.asType(resultType: KCl
* @author Mark Paluch
* @since 2.0
*/
inline fun <reified T : Any> ReactiveFindOperation.FindWithProjection<T>.asType(): ReactiveFindOperation.FindWithQuery<T> =
inline fun <reified T : Any> ReactiveFindOperation.FindWithProjection<*>.asType(): ReactiveFindOperation.FindWithQuery<T> =
`as`(T::class.java)

View File

@@ -15,9 +15,8 @@
*/
package org.springframework.data.mongodb.config;
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assumptions.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@@ -26,6 +25,7 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.mongodb.util.MongoClientVersion;
import org.springframework.util.StringUtils;
import com.mongodb.MongoCredential;
@@ -34,6 +34,7 @@ import com.mongodb.MongoCredential;
* Unit tests for {@link MongoCredentialPropertyEditor}.
*
* @author Christoph Strobl
* @author Stephen Tyler Conrad
*/
public class MongoCredentialPropertyEditorUnitTests {
@@ -54,6 +55,10 @@ public class MongoCredentialPropertyEditorUnitTests {
static final String USER_4_ENCODED_PWD;
static final String USER_4_DB = "targaryen";
static final String USER_5_NAME = "lyanna";
static final String USER_5_PWD = "random?password";
static final String USER_5_DB = "mormont";
static final String USER_1_AUTH_STRING = USER_1_NAME + ":" + USER_1_PWD + "@" + USER_1_DB;
static final String USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM = USER_1_AUTH_STRING + "?uri.authMechanism=PLAIN";
@@ -66,6 +71,13 @@ public class MongoCredentialPropertyEditorUnitTests {
static final String USER_4_AUTH_STRING;
static final String USER_5_AUTH_STRING = USER_5_NAME + ":" + USER_5_PWD + "@" + USER_5_DB;
static final String USER_5_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM = USER_5_AUTH_STRING + "?uri.authMechanism=PLAIN";
static final String USER_5_AUTH_STRING_WITH_QUERY_ARGS = USER_5_AUTH_STRING + "?uri.authMechanism=PLAIN&foo=&bar";
static final String SCRAM_SHA_256_AUTH_STRING = USER_1_NAME + ":" + USER_1_PWD + "@" + USER_1_DB
+ "?uri.authMechanism=SCRAM-SHA-256";
static final MongoCredential USER_1_CREDENTIALS = MongoCredential.createCredential(USER_1_NAME, USER_1_DB,
USER_1_PWD.toCharArray());
static final MongoCredential USER_1_CREDENTIALS_PLAIN_AUTH = MongoCredential.createPlainCredential(USER_1_NAME,
@@ -81,6 +93,11 @@ public class MongoCredentialPropertyEditorUnitTests {
static final MongoCredential USER_4_CREDENTIALS = MongoCredential.createCredential(USER_4_PLAIN_NAME, USER_4_DB,
USER_4_PLAIN_PWD.toCharArray());
static final MongoCredential USER_5_CREDENTIALS = MongoCredential.createCredential(USER_5_NAME, USER_5_DB,
USER_5_PWD.toCharArray());
static final MongoCredential USER_5_CREDENTIALS_PLAIN_AUTH = MongoCredential.createPlainCredential(USER_5_NAME,
USER_5_DB, USER_5_PWD.toCharArray());
MongoCredentialPropertyEditor editor;
static {
@@ -108,7 +125,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(null);
assertThat(editor.getValue(), nullValue());
assertThat(getValue()).isNull();
}
@Test // DATAMONGO-1158
@@ -116,7 +133,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(" ");
assertThat(editor.getValue(), nullValue());
assertThat(getValue()).isNull();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1158
@@ -135,7 +152,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(USER_1_AUTH_STRING);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS);
}
@Test // DATAMONGO-1158
@@ -144,7 +161,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH));
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH);
}
@Test // DATAMONGO-1158
@@ -154,38 +171,37 @@ public class MongoCredentialPropertyEditorUnitTests {
editor
.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(USER_1_AUTH_STRING, USER_2_AUTH_STRING)));
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS);
}
@Test // DATAMONGO-1158
@SuppressWarnings("unchecked")
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndAuthOptions() {
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(
USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING_WITH_MONGODB_CR_AUTH_MECHANISM)));
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays
.asList(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING_WITH_MONGODB_CR_AUTH_MECHANISM)));
assertThat((List<MongoCredential>) editor.getValue(),
contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS_CR_AUTH));
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS_CR_AUTH);
}
@Test // DATAMONGO-1158
@SuppressWarnings("unchecked")
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndMixedOptions() {
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(
USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING)));
editor.setAsText(StringUtils.collectionToCommaDelimitedString(
Arrays.asList(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING)));
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS);
}
@Test // DATAMONGO-1257
@SuppressWarnings("unchecked")
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() {
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList("'" + USER_1_AUTH_STRING + "'", "'"
+ USER_2_AUTH_STRING + "'")));
editor.setAsText(StringUtils.collectionToCommaDelimitedString(
Arrays.asList("'" + USER_1_AUTH_STRING + "'", "'" + USER_2_AUTH_STRING + "'")));
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS);
}
@Test // DATAMONGO-1257
@@ -194,7 +210,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText("'" + USER_1_AUTH_STRING + "'");
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS));
assertThat(getValue()).contains(USER_1_CREDENTIALS);
}
@Test // DATAMONGO-1257
@@ -203,7 +219,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText(USER_3_AUTH_STRING_WITH_X509_AUTH_MECHANISM);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_3_CREDENTIALS_X509_AUTH));
assertThat(getValue()).contains(USER_3_CREDENTIALS_X509_AUTH);
}
@Test // DATAMONGO-1257
@@ -212,7 +228,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText("tyrion?uri.authMechanism=MONGODB-X509");
assertThat((List<MongoCredential>) editor.getValue(), contains(MongoCredential.createMongoX509Credential("tyrion")));
assertThat(getValue()).contains(MongoCredential.createMongoX509Credential("tyrion"));
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1257
@@ -220,7 +236,7 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText("tyrion?uri.authMechanism=MONGODB-CR");
editor.getValue();
getValue();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1257
@@ -228,15 +244,54 @@ public class MongoCredentialPropertyEditorUnitTests {
editor.setAsText("tyrion@?uri.authMechanism=MONGODB-CR");
editor.getValue();
getValue();
}
@Test // DATAMONGO-1317
@SuppressWarnings("unchecked")
public void encodedUserNameAndPasswrodShouldBeDecoded() throws UnsupportedEncodingException {
public void encodedUserNameAndPasswordShouldBeDecoded() {
editor.setAsText(USER_4_AUTH_STRING);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_4_CREDENTIALS));
assertThat(getValue()).contains(USER_4_CREDENTIALS);
}
@Test // DATAMONGO-2016
@SuppressWarnings("unchecked")
public void passwordWithQuestionMarkShouldNotBeInterpretedAsOptionString() {
editor.setAsText(USER_5_AUTH_STRING);
assertThat(getValue()).contains(USER_5_CREDENTIALS);
}
@Test // DATAMONGO-2016
@SuppressWarnings("unchecked")
public void passwordWithQuestionMarkShouldNotBreakParsingOfOptionString() {
editor.setAsText(USER_5_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM);
assertThat(getValue()).contains(USER_5_CREDENTIALS_PLAIN_AUTH);
}
@Test // DATAMONGO-2051
public void shouldReturnScramSha256Credentials() {
assumeThat(MongoClientVersion.isMongo38Driver()).isTrue();
editor.setAsText(SCRAM_SHA_256_AUTH_STRING);
assertThat(getValue()).isNotEmpty();
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-2016
@SuppressWarnings("unchecked")
public void failsGracefullyOnEmptyQueryArgument() {
editor.setAsText(USER_5_AUTH_STRING_WITH_QUERY_ARGS);
}
@SuppressWarnings("unchecked")
private List<MongoCredential> getValue() {
return (List<MongoCredential>) editor.getValue();
}
}

View File

@@ -90,6 +90,7 @@ import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.MapReduceAction;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.UpdateResult;
@@ -139,6 +140,9 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
when(mapReduceIterable.sort(Mockito.any())).thenReturn(mapReduceIterable);
when(mapReduceIterable.iterator()).thenReturn(cursor);
when(mapReduceIterable.filter(any())).thenReturn(mapReduceIterable);
when(mapReduceIterable.collectionName(any())).thenReturn(mapReduceIterable);
when(mapReduceIterable.databaseName(any())).thenReturn(mapReduceIterable);
when(mapReduceIterable.action(any())).thenReturn(mapReduceIterable);
this.mappingContext = new MongoMappingContext();
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
@@ -774,6 +778,52 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-2027
public void mapReduceShouldUseOutputCollectionWhenPresent() {
template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection"),
AutogenerateableId.class);
verify(mapReduceIterable).collectionName(eq("out-collection"));
}
@Test // DATAMONGO-2027
public void mapReduceShouldNotUseOutputCollectionForInline() {
template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection").outputTypeInline(),
AutogenerateableId.class);
verify(mapReduceIterable, never()).collectionName(any());
}
@Test // DATAMONGO-2027
public void mapReduceShouldUseOutputActionWhenPresent() {
template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection").outputTypeMerge(),
AutogenerateableId.class);
verify(mapReduceIterable).action(eq(MapReduceAction.MERGE));
}
@Test // DATAMONGO-2027
public void mapReduceShouldUseOutputDatabaseWhenPresent() {
template.mapReduce("", "", "",
MapReduceOptions.options().outputDatabase("out-database").outputCollection("out-collection").outputTypeMerge(),
AutogenerateableId.class);
verify(mapReduceIterable).databaseName(eq("out-database"));
}
@Test // DATAMONGO-2027
public void mapReduceShouldNotUseOutputDatabaseForInline() {
template.mapReduce("", "", "", MapReduceOptions.options().outputDatabase("out-database").outputTypeInline(),
AutogenerateableId.class);
verify(mapReduceIterable, never()).databaseName(any());
}
@Test // DATAMONGO-1518
public void geoNearShouldUseCollationWhenPresent() {

View File

@@ -0,0 +1,225 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import static org.assertj.core.api.Assertions.*;
import org.bson.Document;
import org.junit.Test;
/**
* Unit tests for {@link ConvertOperators}.
*
* @author Christoph Strobl
* @currentRead Royal Assassin - Robin Hobb
*/
public class ConvertOperatorsUnitTests {
static final String EXPRESSION_STRING = "{ \"$molly\" : \"chandler\" }";
static final Document EXPRESSION_DOC = Document.parse(EXPRESSION_STRING);
static final AggregationExpression EXPRESSION = context -> EXPRESSION_DOC;
@Test // DATAMONGO-2048
public void convertToUsingStringIdentifier() {
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\" } } "));
}
@Test // DATAMONGO-2048
public void convertToUsingIntIdentifier() {
assertThat(ConvertOperators.valueOf("shrewd").convertTo(1).toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : 1 } } "));
}
@Test // DATAMONGO-2048
public void convertToUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToTypeOf("fitz").toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"$fitz\" } } "));
}
@Test // DATAMONGO-2048
public void convertToUsingExpression() {
assertThat(ConvertOperators.valueOf("shrewd").convertToTypeOf(EXPRESSION).toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : " + EXPRESSION_STRING + " } } "));
}
@Test // DATAMONGO-2048
public void convertToWithOnErrorValue() {
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onErrorReturn("foo")
.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(
Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onError\" : \"foo\" } } "));
}
@Test // DATAMONGO-2048
public void convertToWithOnErrorValueOfField() {
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onErrorReturnValueOf("verity")
.toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document
.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onError\" : \"$verity\" } } "));
}
@Test // DATAMONGO-2048
public void convertToWithOnErrorValueOfExpression() {
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onErrorReturnValueOf(EXPRESSION)
.toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onError\" : "
+ EXPRESSION_STRING + " } } "));
}
@Test // DATAMONGO-2048
public void convertToWithOnNullValue() {
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onNullReturn("foo")
.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(
Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onNull\" : \"foo\" } } "));
}
@Test // DATAMONGO-2048
public void convertToWithOnNullValueOfField() {
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onNullReturnValueOf("verity")
.toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document
.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onNull\" : \"$verity\" } } "));
}
@Test // DATAMONGO-2048
public void convertToWithOnNullValueOfExpression() {
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onNullReturnValueOf(EXPRESSION)
.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(Document.parse(
"{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onNull\" : " + EXPRESSION_STRING + " } } "));
}
@Test // DATAMONGO-2048
public void toBoolUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToBoolean().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toBool: \"$shrewd\" } "));
}
@Test // DATAMONGO-2048
public void toBoolUsingExpression() {
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToBoolean().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toBool: " + EXPRESSION_STRING + " } "));
}
@Test // DATAMONGO-2048
public void toDateUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToDate().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toDate: \"$shrewd\" } "));
}
@Test // DATAMONGO-2048
public void toDateUsingExpression() {
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToDate().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toDate: " + EXPRESSION_STRING + " } "));
}
@Test // DATAMONGO-2048
public void toDecimalUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToDecimal().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toDecimal: \"$shrewd\" } "));
}
@Test // DATAMONGO-2048
public void toDecimalUsingExpression() {
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToDecimal().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toDecimal: " + EXPRESSION_STRING + " } "));
}
@Test // DATAMONGO-2048
public void toDoubleUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToDouble().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toDouble: \"$shrewd\" } "));
}
@Test // DATAMONGO-2048
public void toDoubleUsingExpression() {
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToDouble().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toDouble: " + EXPRESSION_STRING + " } "));
}
@Test // DATAMONGO-2048
public void toIntUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToInt().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toInt: \"$shrewd\" } "));
}
@Test // DATAMONGO-2048
public void toIntUsingExpression() {
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToInt().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toInt: " + EXPRESSION_STRING + " } "));
}
@Test // DATAMONGO-2048
public void toLongUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToLong().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toLong: \"$shrewd\" } "));
}
@Test // DATAMONGO-2048
public void toLongUsingExpression() {
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToLong().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toLong: " + EXPRESSION_STRING + " } "));
}
@Test // DATAMONGO-2048
public void toObjectIdUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToObjectId().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toObjectId: \"$shrewd\" } "));
}
@Test // DATAMONGO-2048
public void toObjectIdUsingExpression() {
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToObjectId().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toObjectId: " + EXPRESSION_STRING + " } "));
}
@Test // DATAMONGO-2048
public void toStringUsingFieldReference() {
assertThat(ConvertOperators.valueOf("shrewd").convertToString().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toString: \"$shrewd\" } "));
}
@Test // DATAMONGO-2048
public void toStringUsingExpression() {
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToString().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $toString: " + EXPRESSION_STRING + " } "));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,7 @@
*/
package org.springframework.data.mongodb.core.aggregation;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.*;
import org.bson.Document;
import org.junit.Test;
@@ -25,9 +24,10 @@ import org.springframework.data.mongodb.core.query.NearQuery;
/**
* Unit tests for {@link GeoNearOperation}.
*
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class GeoNearOperationUnitTests {
@@ -41,6 +41,16 @@ public class GeoNearOperationUnitTests {
Document nearClause = DocumentTestUtils.getAsDocument(document, "$geoNear");
Document expected = new Document(query.toDocument()).append("distanceField", "distance");
assertThat(nearClause, is(expected));
assertThat(nearClause).isEqualTo(expected);
}
@Test // DATAMONGO-2050
public void rendersNearQueryWithKeyCorrectly() {
NearQuery query = NearQuery.near(10.0, 10.0);
GeoNearOperation operation = new GeoNearOperation(query, "distance").useIndex("geo-index-1");
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(DocumentTestUtils.getAsDocument(document, "$geoNear")).containsEntry("key", "geo-index-1");
}
}

View File

@@ -15,19 +15,15 @@
*/
package org.springframework.data.mongodb.core.aggregation;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import static org.springframework.data.mongodb.test.util.Assertions.*;
import java.util.Arrays;
import org.bson.Document;
import org.junit.Test;
import org.springframework.data.mongodb.core.Person;
import org.springframework.data.mongodb.core.query.Criteria;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
/**
* Unit tests for {@link GraphLookupOperation}.
*
@@ -54,8 +50,7 @@ public class GraphLookupOperationUnitTests {
.as("reportingHierarchy");
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document,
isBsonObject().containing("$graphLookup.depthField", "depth").containing("$graphLookup.maxDepth", 42L));
assertThat(document).containsEntry("$graphLookup.depthField", "depth").containsEntry("$graphLookup.maxDepth", 42L);
}
@Test // DATAMONGO-1551
@@ -70,8 +65,7 @@ public class GraphLookupOperationUnitTests {
.as("reportingHierarchy");
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document,
isBsonObject().containing("$graphLookup.restrictSearchWithMatch", new Document("key", "value")));
assertThat(document).containsEntry("$graphLookup.restrictSearchWithMatch", new Document("key", "value"));
}
@Test // DATAMONGO-1551
@@ -86,9 +80,9 @@ public class GraphLookupOperationUnitTests {
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document,
is(Document.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", \"$boss\"], "
+ "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }")));
assertThat(document)
.isEqualTo(Document.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", \"$boss\"], "
+ "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"));
}
@Test // DATAMONGO-1551
@@ -103,9 +97,8 @@ public class GraphLookupOperationUnitTests {
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document,
is(Document.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", { $literal: \"$boss\"}], "
+ "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }")));
assertThat(document).containsEntry("$graphLookup.startWith",
Arrays.asList("$reportsTo", new Document("$literal", "$boss")));
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1551
@@ -131,7 +124,39 @@ public class GraphLookupOperationUnitTests {
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document, is(Document.parse("{ $graphLookup : { from: \"employees\", startWith: { $literal: \"hello\"}, "
+ "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }")));
assertThat(document).containsEntry("$graphLookup.startWith", new Document("$literal", "hello"));
}
@Test // DATAMONGO-2096
public void connectFromShouldUseTargetFieldInsteadOfAlias() {
AggregationOperation graphLookupOperation = Aggregation.graphLookup("user").startWith("contacts.userId")
.connectFrom("contacts.userId").connectTo("_id").depthField("numConnections").as("connections");
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document).containsEntry("$graphLookup.startWith", "$contacts.userId");
}
@Test // DATAMONGO-2096
public void connectToShouldUseTargetFieldInsteadOfAlias() {
AggregationOperation graphLookupOperation = Aggregation.graphLookup("user").startWith("contacts.userId")
.connectFrom("userId").connectTo("connectto.field").depthField("numConnections").as("connections");
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document).containsEntry("$graphLookup.connectToField", "connectto.field");
}
@Test // DATAMONGO-2096
public void depthFieldShouldUseTargetFieldInsteadOfAlias() {
AggregationOperation graphLookupOperation = Aggregation.graphLookup("user").startWith("contacts.userId")
.connectFrom("contacts.userId").connectTo("_id").depthField("foo.bar").as("connections");
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document).containsEntry("$graphLookup.depthField", "foo.bar");
}
}

View File

@@ -1300,6 +1300,14 @@ public class ProjectionOperationUnitTests {
Document.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"));
}
@Test // DATAMONGO-2047
public void shouldRenderDateToStringWithoutFormatOption() {
Document agg = project().and("date").dateAsFormattedString().as("time").toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg).isEqualTo(Document.parse("{ $project: { time: { $dateToString: { date: \"$date\" } } } }"));
}
@Test // DATAMONGO-1536
public void shouldRenderDateToStringAggregationExpression() {
@@ -1310,7 +1318,7 @@ public class ProjectionOperationUnitTests {
Document.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"));
}
@Test // DATAMONGO-1834
@Test // DATAMONGO-1834, DATAMONGO-2047
public void shouldRenderDateToStringAggregationExpressionWithTimezone() {
Document agg = project()
@@ -1319,6 +1327,47 @@ public class ProjectionOperationUnitTests {
assertThat(agg).isEqualTo(Document.parse(
"{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\", \"timezone\" : \"America/Chicago\" } } } } } }"));
Document removedTimezone = project().and(DateOperators.dateOf("date")
.withTimezone(Timezone.valueOf("America/Chicago")).toString("%H:%M:%S:%L").withTimezone(Timezone.none()))
.as("time").toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(removedTimezone).isEqualTo(
Document.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } } } }"));
}
@Test // DATAMONGO-2047
public void shouldRenderDateToStringWithOnNull() {
Document agg = project()
.and(DateOperators.dateOf("date").toStringWithDefaultFormat().onNullReturnValueOf("fallback-field")).as("time")
.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg).isEqualTo(Document
.parse("{ $project: { time: { $dateToString: { date: \"$date\", \"onNull\" : \"$fallback-field\" } } } }"));
}
@Test // DATAMONGO-2047
public void shouldRenderDateToStringWithOnNullExpression() {
Document agg = project()
.and(DateOperators.dateOf("date").toStringWithDefaultFormat()
.onNullReturnValueOf(LiteralOperators.valueOf("my-literal").asLiteral()))
.as("time").toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg).isEqualTo(Document.parse(
"{ $project: { time: { $dateToString: { date: \"$date\", \"onNull\" : { \"$literal\": \"my-literal\"} } } } }"));
}
@Test // DATAMONGO-2047
public void shouldRenderDateToStringWithOnNullAndTimezone() {
Document agg = project().and(DateOperators.dateOf("date").toStringWithDefaultFormat()
.onNullReturnValueOf("fallback-field").withTimezone(Timezone.ofField("foo"))).as("time")
.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg).isEqualTo(Document.parse(
"{ $project: { time: { $dateToString: { date: \"$date\", \"onNull\" : \"$fallback-field\", \"timezone\": \"$foo\" } } } }"));
}
@Test // DATAMONGO-1536
@@ -2020,6 +2069,16 @@ public class ProjectionOperationUnitTests {
"{ $project : { newDate: { $dateFromString: { dateString : \"2017-02-08T12:10:40.787\", timezone : \"America/Chicago\" } } } }"));
}
@Test // DATAMONGO-2047
public void shouldRenderDateFromStringWithFormat() {
Document agg = project().and(DateOperators.dateFromString("2017-02-08T12:10:40.787").withFormat("dd/mm/yyyy"))
.as("newDate").toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg).isEqualTo(Document.parse(
"{ $project : { newDate: { $dateFromString: { dateString : \"2017-02-08T12:10:40.787\", format : \"dd/mm/yyyy\" } } } }"));
}
private static Document exctractOperation(String field, Document fromProjectClause) {
return (Document) fromProjectClause.get(field);
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import static org.assertj.core.api.Assertions.*;
import org.bson.Document;
import org.junit.Test;
/**
* Unit test for {@link StringOperators}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @currentRead Royal Assassin - Robin Hobb
*/
public class StringOperatorsUnitTests {
static final String EXPRESSION_STRING = "{ \"$fitz\" : \"chivalry\" }";
static final Document EXPRESSION_DOC = Document.parse(EXPRESSION_STRING);
static final AggregationExpression EXPRESSION = context -> EXPRESSION_DOC;
@Test // DATAMONGO-2049
public void shouldRenderTrim() {
assertThat(StringOperators.valueOf("shrewd").trim().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $trim: { \"input\" : \"$shrewd\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderTrimForExpression() {
assertThat(StringOperators.valueOf(EXPRESSION).trim().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $trim: { \"input\" : " + EXPRESSION_STRING + " } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderTrimWithChars() {
assertThat(StringOperators.valueOf("shrewd").trim("sh").toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $trim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderTrimWithCharsExpression() {
assertThat(StringOperators.valueOf("shrewd").trim(EXPRESSION).toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $trim: { \"input\" : \"$shrewd\", \"chars\" : " + EXPRESSION_STRING + " } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderTrimLeft() {
assertThat(StringOperators.valueOf("shrewd").trim().left().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderTrimLeftWithChars() {
assertThat(StringOperators.valueOf("shrewd").trim("sh").left().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderTrimRight() {
assertThat(StringOperators.valueOf("shrewd").trim().right().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderTrimRightWithChars() {
assertThat(StringOperators.valueOf("shrewd").trim("sh").right().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderLTrim() {
assertThat(StringOperators.valueOf("shrewd").ltrim().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderLTrimForExpression() {
assertThat(StringOperators.valueOf(EXPRESSION).ltrim().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : " + EXPRESSION_STRING + " } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderLTrimWithChars() {
assertThat(StringOperators.valueOf("shrewd").ltrim("sh").toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderLTrimWithCharsExpression() {
assertThat(StringOperators.valueOf("shrewd").ltrim(EXPRESSION).toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\", \"chars\" : " + EXPRESSION_STRING + " } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderRTrim() {
assertThat(StringOperators.valueOf("shrewd").rtrim().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderRTrimForExpression() {
assertThat(StringOperators.valueOf(EXPRESSION).rtrim().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : " + EXPRESSION_STRING + " } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderRTrimWithChars() {
assertThat(StringOperators.valueOf("shrewd").rtrim("sh").toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
}
@Test // DATAMONGO-2049
public void shouldRenderRTrimWithCharsExpression() {
assertThat(StringOperators.valueOf("shrewd").rtrim(EXPRESSION).toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\", \"chars\" : " + EXPRESSION_STRING + " } } "));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2017 the original author or authors.
* Copyright 2011-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,28 +26,14 @@ import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.DocumentTestUtils.*;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.*;
import org.bson.types.ObjectId;
import org.hamcrest.Matcher;
@@ -1886,6 +1872,36 @@ public class MappingMongoConverterUnitTests {
assertThat(result.nestedFloats).isEqualTo(new float[][][] { { { 1.0f, 2.0f } } });
}
@Test // DATAMONGO-2011
public void readsNestedListsToObjectCorrectly() {
List<String> values = Arrays.asList("ONE", "TWO");
org.bson.Document source = new org.bson.Document("value", Collections.singletonList(values));
assertThat(converter.read(Attribute.class, source).value).isInstanceOf(List.class);
}
@Test // DATAMONGO-2043
public void omitsTypeHintWhenWritingSimpleTypes() {
org.bson.Document target = new org.bson.Document();
converter.write(new org.bson.Document("value", "FitzChivalry"), target);
assertThat(target).doesNotContainKeys("_class");
}
@Test // DATAMONGO-2135
public void addsEqualObjectsToCollection() {
org.bson.Document itemDocument = new org.bson.Document("itemKey", "123");
org.bson.Document orderDocument = new org.bson.Document("items",
Arrays.asList(itemDocument, itemDocument, itemDocument));
Order order = converter.read(Order.class, orderDocument);
assertThat(order.items).hasSize(3);
}
static class GenericType<T> {
T content;
}
@@ -2266,4 +2282,15 @@ public class MappingMongoConverterUnitTests {
static class WithNestedLists {
float[][][] nestedFloats;
}
// DATAMONGO-2135
@EqualsAndHashCode // equality check by fields
static class SomeItem {
String itemKey;
}
static class Order {
Collection<SomeItem> items = new ArrayList<>();
}
}

View File

@@ -277,6 +277,20 @@ public class UpdateMapperUnitTests {
assertThat(getAsDocument(push, "key")).containsKey("$each");
}
@Test // DATAMONGO-943, DATAMONGO-2055
public void updatePushEachAtNegativePositionWorksCorrectly() {
Update update = new Update().push("key").atPosition(-2).each(Arrays.asList("Arya", "Arry", "Weasel"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "key");
assertThat(key.containsKey("$position")).isTrue();
assertThat(key.get("$position")).isEqualTo(-2);
}
@Test // DATAMONGO-943
public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionFirst() {

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb.core.mapreduce;
import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.mapreduce.MapReduceOptions.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
@@ -45,13 +45,15 @@ import com.mongodb.client.MongoCollection;
*
* @author Mark Pollack
* @author Thomas Darimont
* @author Mark Paluch
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
public class MapReduceTests {
private String mapFunction = "function(){ for ( var i=0; i<this.x.length; i++ ){ emit( this.x[i] , 1 ); } }";
private String reduceFunction = "function(key,values){ var sum=0; for( var i=0; i<values.length; i++ ) sum += values[i]; return sum;}";
private static final String MAP_FUNCTION = "function(){ for ( var i=0; i<this.x.length; i++ ){ emit( this.x[i] , 1 ); } }";
private static final String REDUCE_FUNCTION = "function(key,values){ var sum=0; for( var i=0; i<values.length; i++ ) sum += values[i]; return sum;}";
@Autowired MongoTemplate template;
@Autowired MongoTemplate mongoTemplate;
@@ -67,73 +69,229 @@ public class MapReduceTests {
}
protected void cleanDb() {
template.dropCollection(template.getCollectionName(ValueObject.class));
template.dropCollection("jmr2");
template.dropCollection("jmr2_out");
template.dropCollection("jmr1_out");
template.dropCollection("jmr1");
template.dropCollection("jmrWithGeo");
template.getMongoDbFactory().getDb("jmr1-out-db").drop();
}
@Test
@Test // DATADOC-7
@Ignore
public void testForDocs() {
createMapReduceData();
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", mapFunction, reduceFunction,
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
ValueObject.class);
for (ValueObject valueObject : results) {
System.out.println(valueObject);
}
}
@Test
@Test // DATAMONGO-260
public void testIssue260() {
createContentAndVersionData();
String map = "function () { emit(this.document_id, this.version); }";
String reduce = "function (key, values) { return Math.max.apply(Math, values); }";
MapReduceResults<ContentAndVersion> results = mongoTemplate.mapReduce("jmr2", map, reduce,
new MapReduceOptions().outputCollection("jmr2_out"), ContentAndVersion.class);
int size = 0;
assertThat(results).hasSize(3);
for (ContentAndVersion cv : results) {
if ("Resume".equals(cv.getId())) {
assertEquals(6, cv.getValue().longValue());
assertThat(cv.getValue().longValue()).isEqualTo(6);
}
if ("Schema".equals(cv.getId())) {
assertEquals(2, cv.getValue().longValue());
assertThat(cv.getValue().longValue()).isEqualTo(2);
}
if ("mongoDB How-To".equals(cv.getId())) {
assertEquals(2, cv.getValue().longValue());
assertThat(cv.getValue().longValue()).isEqualTo(2);
}
size++;
}
assertEquals(3, size);
}
@Test
@Test // DATAMONGO-260
public void testIssue260Part2() {
createNumberAndVersionData();
String map = "function () { emit(this.number, this.version); }";
String reduce = "function (key, values) { return Math.max.apply(Math, values); }";
MapReduceResults<NumberAndVersion> results = mongoTemplate.mapReduce("jmr2", map, reduce,
new MapReduceOptions().outputCollection("jmr2_out"), NumberAndVersion.class);
int size = 0;
for (NumberAndVersion nv : results) {
if ("1".equals(nv.getId())) {
assertEquals(2, nv.getValue().longValue());
assertThat(nv.getValue().longValue()).isEqualTo(2);
}
if ("2".equals(nv.getId())) {
assertEquals(6, nv.getValue().longValue());
assertThat(nv.getValue().longValue()).isEqualTo(6);
}
if ("3".equals(nv.getId())) {
assertEquals(2, nv.getValue().longValue());
assertThat(nv.getValue().longValue()).isEqualTo(2);
}
size++;
}
assertEquals(3, size);
assertThat(results).hasSize(3);
}
@Test // DATADOC-7, DATAMONGO-2027
public void testMapReduce() {
performMapReduce(false, false);
List<ValueObject> results = mongoTemplate.find(new Query(), ValueObject.class, "jmr1_out");
assertMapReduceResults(copyToMap(results));
}
@Test // DATADOC-7, DATAMONGO-2027
public void testMapReduceInline() {
performMapReduce(true, false);
assertThat(template.collectionExists("jmr1_out")).isFalse();
}
@Test // DATAMONGO-2027
public void mapReduceWithOutputDatabaseShouldWorkCorrectly() {
createMapReduceData();
mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
options().outputDatabase("jmr1-out-db").outputCollection("jmr1-out"), ValueObject.class);
assertThat(template.getMongoDbFactory().getDb("jmr1-out-db").listCollectionNames().into(new ArrayList<>()))
.contains("jmr1-out");
}
@Test // DATADOC-7
public void testMapReduceWithQuery() {
performMapReduce(false, true);
}
@Test // DATADOC-7
public void testMapReduceInlineWithScope() {
createMapReduceData();
Map<String, Object> scopeVariables = new HashMap<String, Object>();
scopeVariables.put("exclude", "a");
String mapWithExcludeFunction = "function(){ for ( var i=0; i<this.x.length; i++ ){ if(this.x[i] != exclude) emit( this.x[i] , 1 ); } }";
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", mapWithExcludeFunction, REDUCE_FUNCTION,
new MapReduceOptions().scopeVariables(scopeVariables).outputTypeInline(), ValueObject.class);
assertThat(copyToMap(results)) //
.hasSize(3) //
.containsEntry("b", 2F) //
.containsEntry("c", 2F) //
.containsEntry("d", 1F);
}
@Test // DATADOC-7
public void testMapReduceExcludeQuery() {
createMapReduceData();
Query query = new Query(where("x").ne(new String[] { "a", "b" }));
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce(query, "jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
ValueObject.class);
assertThat(copyToMap(results)) //
.hasSize(3) //
.containsEntry("b", 1F) //
.containsEntry("c", 2F) //
.containsEntry("d", 1F);
}
@Test // DATAMONGO-938
public void mapReduceShouldUseQueryMapper() {
MongoCollection<Document> c = mongoTemplate.getDb().getCollection("jmrWithGeo", Document.class);
c.insertOne(new Document("x", Arrays.asList("a", "b")).append("loc", Arrays.asList(0D, 0D)));
c.insertOne(new Document("x", Arrays.asList("b", "c")).append("loc", Arrays.asList(0D, 0D)));
c.insertOne(new Document("x", Arrays.asList("c", "d")).append("loc", Arrays.asList(0D, 0D)));
Query query = new Query(where("x").ne(new String[] { "a", "b" }).and("loc")
.within(new Box(new double[] { 0, 0 }, new double[] { 1, 1 })));
MapReduceResults<ValueObject> results = template.mapReduce(query, "jmrWithGeo", MAP_FUNCTION, REDUCE_FUNCTION,
ValueObject.class);
assertThat(copyToMap(results)) //
.hasSize(3) //
.containsEntry("b", 1F) //
.containsEntry("c", 2F) //
.containsEntry("d", 1F);
}
private void performMapReduce(boolean inline, boolean withQuery) {
createMapReduceData();
MapReduceResults<ValueObject> results;
if (inline) {
if (withQuery) {
results = mongoTemplate.mapReduce(new Query(), "jmr1", "classpath:map.js", "classpath:reduce.js",
ValueObject.class);
} else {
results = mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION, ValueObject.class);
}
} else {
if (withQuery) {
results = mongoTemplate.mapReduce(new Query(), "jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
options().outputCollection("jmr1_out"), ValueObject.class);
} else {
results = mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
new MapReduceOptions().outputCollection("jmr1_out"), ValueObject.class);
}
}
assertMapReduceResults(copyToMap(results));
}
private void createMapReduceData() {
MongoCollection<Document> c = mongoTemplate.getDb().getCollection("jmr1", Document.class);
c.insertOne(new Document("x", Arrays.asList("a", "b")));
c.insertOne(new Document("x", Arrays.asList("b", "c")));
c.insertOne(new Document("x", Arrays.asList("c", "d")));
}
private Map<String, Float> copyToMap(Iterable<ValueObject> results) {
List<ValueObject> valueObjects = new ArrayList<>();
for (ValueObject valueObject : results) {
valueObjects.add(valueObject);
}
Map<String, Float> m = new HashMap<>();
for (ValueObject vo : valueObjects) {
m.put(vo.getId(), vo.getValue());
}
return m;
}
private void assertMapReduceResults(Map<String, Float> map) {
assertThat(map) //
.hasSize(4) //
.containsEntry("a", 1F) //
.containsEntry("b", 2F) //
.containsEntry("c", 2F) //
.containsEntry("d", 1F);
}
private void createNumberAndVersionData() {
NumberAndVersion nv1 = new NumberAndVersion();
nv1.setNumber(1L);
nv1.setVersion(1L);
@@ -168,7 +326,7 @@ public class MapReduceTests {
{ "_id" : 3, "document_id" : "Resume", "author" : "Author", "content" : "...", "version" : 6 }
{ "_id" : 4, "document_id" : "Schema", "author" : "Someone Else", "content" : "...", "version" : 0.9 }
{ "_id" : 5, "document_id" : "Schema", "author" : "Someone Else", "content" : "...", "version" : 1 }
*/
ContentAndVersion cv1 = new ContentAndVersion();
cv1.setDocumentId("mongoDB How-To");
@@ -204,129 +362,5 @@ public class MapReduceTests {
cv5.setContent("...");
cv5.setVersion(2L);
template.save(cv5, "jmr2");
}
@Test
public void testMapReduce() {
performMapReduce(false, false);
}
@Test
public void testMapReduceInline() {
performMapReduce(true, false);
}
@Test
public void testMapReduceWithQuery() {
performMapReduce(false, true);
}
@Test
public void testMapReduceInlineWithScope() {
createMapReduceData();
Map<String, Object> scopeVariables = new HashMap<String, Object>();
scopeVariables.put("exclude", "a");
String mapWithExcludeFunction = "function(){ for ( var i=0; i<this.x.length; i++ ){ if(this.x[i] != exclude) emit( this.x[i] , 1 ); } }";
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", mapWithExcludeFunction, reduceFunction,
new MapReduceOptions().scopeVariables(scopeVariables).outputTypeInline(), ValueObject.class);
Map<String, Float> m = copyToMap(results);
assertEquals(3, m.size());
assertEquals(2, m.get("b").intValue());
assertEquals(2, m.get("c").intValue());
assertEquals(1, m.get("d").intValue());
}
@Test
public void testMapReduceExcludeQuery() {
createMapReduceData();
Query query = new Query(where("x").ne(new String[] { "a", "b" }));
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce(query, "jmr1", mapFunction, reduceFunction,
ValueObject.class);
Map<String, Float> m = copyToMap(results);
assertEquals(3, m.size());
assertEquals(1, m.get("b").intValue());
assertEquals(2, m.get("c").intValue());
assertEquals(1, m.get("d").intValue());
}
@Test // DATAMONGO-938
public void mapReduceShouldUseQueryMapper() {
MongoCollection<Document> c = mongoTemplate.getDb().getCollection("jmrWithGeo", Document.class);
c.insertOne(new Document("x", Arrays.asList("a", "b")).append("loc", Arrays.<Double> asList(0D, 0D)));
c.insertOne(new Document("x", Arrays.asList("b", "c")).append("loc", Arrays.<Double> asList(0D, 0D)));
c.insertOne(new Document("x", Arrays.asList("c", "d")).append("loc", Arrays.<Double> asList(0D, 0D)));
Query query = new Query(where("x").ne(new String[] { "a", "b" }).and("loc")
.within(new Box(new double[] { 0, 0 }, new double[] { 1, 1 })));
MapReduceResults<ValueObject> results = template.mapReduce(query, "jmrWithGeo", mapFunction, reduceFunction,
ValueObject.class);
Map<String, Float> m = copyToMap(results);
assertEquals(3, m.size());
assertEquals(1, m.get("b").intValue());
assertEquals(2, m.get("c").intValue());
assertEquals(1, m.get("d").intValue());
}
private void performMapReduce(boolean inline, boolean withQuery) {
createMapReduceData();
MapReduceResults<ValueObject> results;
if (inline) {
if (withQuery) {
results = mongoTemplate.mapReduce(new Query(), "jmr1", "classpath:map.js", "classpath:reduce.js",
ValueObject.class);
} else {
results = mongoTemplate.mapReduce("jmr1", mapFunction, reduceFunction, ValueObject.class);
}
} else {
if (withQuery) {
results = mongoTemplate.mapReduce(new Query(), "jmr1", mapFunction, reduceFunction,
options().outputCollection("jmr1_out"), ValueObject.class);
} else {
results = mongoTemplate.mapReduce("jmr1", mapFunction, reduceFunction,
new MapReduceOptions().outputCollection("jmr1_out"), ValueObject.class);
}
}
Map<String, Float> m = copyToMap(results);
assertMapReduceResults(m);
}
private void createMapReduceData() {
MongoCollection<Document> c = mongoTemplate.getDb().getCollection("jmr1", Document.class);
c.insertOne(new Document("x", Arrays.asList("a", "b")));
c.insertOne(new Document("x", Arrays.asList("b", "c")));
c.insertOne(new Document("x", Arrays.asList("c", "d")));
}
private Map<String, Float> copyToMap(MapReduceResults<ValueObject> results) {
List<ValueObject> valueObjects = new ArrayList<ValueObject>();
for (ValueObject valueObject : results) {
valueObjects.add(valueObject);
}
Map<String, Float> m = new HashMap<String, Float>();
for (ValueObject vo : valueObjects) {
m.put(vo.getId(), vo.getValue());
}
return m;
}
private void assertMapReduceResults(Map<String, Float> m) {
assertEquals(4, m.size());
assertEquals(1, m.get("a").intValue());
assertEquals(2, m.get("b").intValue());
assertEquals(2, m.get("c").intValue());
assertEquals(1, m.get("d").intValue());
}
}

View File

@@ -390,11 +390,6 @@ public class UpdateTests {
.isEqualTo(new Document().append("$bit", new Document("key", new Document("xor", 10L))));
}
@Test(expected = IllegalArgumentException.class) // DATAMONGO-943
public void pushShouldThrowExceptionWhenGivenNegativePosition() {
new Update().push("foo").atPosition(-1).each("booh");
}
@Test // DATAMONGO-1346
public void registersMultiplePullAllClauses() {

View File

@@ -15,18 +15,28 @@
*/
package org.springframework.data.mongodb.repository;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.mongodb.core.geo.GeoJson;
import com.querydsl.core.annotations.QueryEmbeddable;
/**
* @author Oliver Gierke
* @author Christoph Strobl
*/
@QueryEmbeddable
@Getter
@Setter
public class Address {
private String street;
private String zipCode;
private String city;
private GeoJson location;
protected Address() {
}
@@ -41,46 +51,4 @@ public class Address {
this.zipCode = zipcode;
this.city = city;
}
/**
* @return the street
*/
public String getStreet() {
return street;
}
/**
* @param street the street to set
*/
public void setStreet(String street) {
this.street = street;
}
/**
* @return the zipCode
*/
public String getZipCode() {
return zipCode;
}
/**
* @param zipCode the zipCode to set
*/
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
/**
* @return the city
*/
public String getCity() {
return city;
}
/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}
}

View File

@@ -313,6 +313,11 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
StepVerifier.create(repository.findFirstByLastname(dave.getLastname())).expectNextCount(1).verifyComplete();
}
@Test // DATAMONGO-2030
public void shouldReturnExistsBy() {
StepVerifier.create(repository.existsByLastname(dave.getLastname())).expectNext(true).verifyComplete();
}
interface ReactivePersonRepository extends ReactiveMongoRepository<Person, String> {
Flux<Person> findByLastname(String lastname);
@@ -340,6 +345,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
Flux<Person> findPersonByLocationNear(Point point, Distance maxDistance);
Mono<Boolean> existsByLastname(String lastname);
Mono<Person> findFirstByLastname(String lastname);
}

View File

@@ -122,6 +122,14 @@ public class ReactiveStringBasedMongoQueryUnitTests {
createQueryForMethod("invalidMethod", String.class);
}
@Test // DATAMONGO-2030
public void shouldSupportExistsProjection() throws Exception {
ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("existsByLastname", String.class);
assertThat(mongoQuery.isExistsQuery(), is(true));
}
@Test // DATAMONGO-1444
public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception {
@@ -260,5 +268,8 @@ public class ReactiveStringBasedMongoQueryUnitTests {
@Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}")
Flux<Person> findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2);
@Query(value = "{ 'lastname' : ?0 }", exists = true)
Mono<Boolean> existsByLastname(String lastname);
}
}

View File

@@ -23,6 +23,7 @@ import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -322,6 +323,21 @@ public class StringBasedMongoQueryUnitTests {
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-2029
public void shouldSupportNonQuotedBinaryCollectionDataReplacement() {
byte[] binaryData = "Matthews".getBytes(StandardCharsets.UTF_8);
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
(Object) Arrays.asList(binaryData));
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsBinaryIn", List.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : { $in: [{'$binary' : '"
+ DatatypeConverter.printBase64Binary(binaryData) + "', '$type' : '" + BSON.B_GENERAL + "'}] }}");
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-1911
public void shouldSupportNonQuotedUUIDReplacement() {
@@ -336,6 +352,23 @@ public class StringBasedMongoQueryUnitTests {
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-2029
public void shouldSupportNonQuotedUUIDCollectionReplacement() {
UUID uuid1 = UUID.fromString("864de43b-e3ea-f1e4-3663-fb8240b659b9");
UUID uuid2 = UUID.fromString("864de43b-cafe-f1e4-3663-fb8240b659b9");
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
(Object) Arrays.asList(uuid1, uuid2));
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsUUIDIn", List.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(
"{'lastname' : { $in: [{ $binary : \"5PHq4zvkTYa5WbZAgvtjNg==\", $type : \"03\" }, { $binary : \"5PH+yjvkTYa5WbZAgvtjNg==\", $type : \"03\" }]}}");
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-1911
public void shouldSupportQuotedUUIDReplacement() {
@@ -557,6 +590,19 @@ public class StringBasedMongoQueryUnitTests {
assertThat(query.getQueryObject(), is(new Document("arg0", null)));
}
@Test // DATAMONGO-2119
public void spelShouldIgnoreJsonParseErrorsForRegex() {
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByPersonLastnameRegex", Person.class);
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
new Person("Molly", "Chandler"));
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
assertThat(query.getQueryObject().toJson(),
is(new BasicQuery("{lastname: {$regex: 'Chandler'}}").getQueryObject().toJson()));
}
private StringBasedMongoQuery createQueryForMethod(String name, Class<?>... parameters) {
try {
@@ -580,9 +626,15 @@ public class StringBasedMongoQueryUnitTests {
@Query("{ 'lastname' : ?0 }")
Person findByLastnameAsBinary(byte[] lastname);
@Query("{ 'lastname' : { $in: ?0} }")
Person findByLastnameAsBinaryIn(List<byte[]> lastname);
@Query("{ 'lastname' : ?0 }")
Person findByLastnameAsUUID(UUID lastname);
@Query("{ 'lastname' : { $in : ?0} }")
Person findByLastnameAsUUIDIn(List<UUID> lastname);
@Query("{ 'lastname' : '?0' }")
Person findByLastnameAsStringUUID(UUID lastname);
@@ -658,10 +710,14 @@ public class StringBasedMongoQueryUnitTests {
@Query("{ 'arg0' : '?0', 'arg1' : '?1s' }")
List<Person> findByWhenQuotedAndSomeStuffAppended(String arg0, String arg1);
@Query("{ 'lastname' : { '$regex' : '^(?0|John ?1|?1)'} }") // use spel or some regex string this is fucking bad
@Query("{ 'lastname' : { '$regex' : '^(?0|John ?1|?1)'} }") // use spel or some regex string this is bad
Person findByLastnameRegex(String lastname, String alternative);
@Query("{ arg0 : ?#{[0]} }")
List<Person> findByUsingSpel(Object arg0);
@Query("{ 'lastname' : { '$regex' : ?#{[0].lastname} } }")
Person findByPersonLastnameRegex(Person key);
}
}

View File

@@ -28,6 +28,8 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.repository.Address;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.QPerson;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
@@ -99,4 +101,21 @@ public class QuerydslMongoPredicateExecutorIntegrationTests {
public void findOneWithPredicateThrowsExceptionForNonUniqueResults() {
repository.findOne(person.firstname.contains("e"));
}
@Test // DATAMONGO-2101
public void readEntityWithGeoJsonValue() {
Address adr1 = new Address("Hauptplatz", "4020", "Linz");
adr1.setLocation(new GeoJsonPoint(48.3063548, 14.2851337));
Person person1 = new Person("Max", "The Mighty");
person1.setAddress(adr1);
operations.save(person1);
List<Person> result = new SpringDataMongodbQuery<>(operations, Person.class).where(person.firstname.eq("Max"))
.fetch();
assertThat(result).containsExactly(person1);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,18 +50,17 @@ class ExecutableFindOperationExtensionsTests {
verify(operation).query(First::class.java)
}
@Test // DATAMONGO-1689
@Test // DATAMONGO-1689, DATAMONGO-2086
fun `ExecutableFindOperation#FindOperationWithProjection#asType(KClass) extension should call its Java counterpart`() {
operationWithProjection.asType(First::class)
verify(operationWithProjection).`as`(First::class.java)
operationWithProjection.asType(User::class)
verify(operationWithProjection).`as`(User::class.java)
}
@Test // DATAMONGO-1689
@Test // DATAMONGO-1689, DATAMONGO-2086
fun `ExecutableFindOperation#FindOperationWithProjection#asType() with reified type parameter extension should call its Java counterpart`() {
operationWithProjection.asType()
verify(operationWithProjection).`as`(First::class.java)
operationWithProjection.asType<User>()
verify(operationWithProjection).`as`(User::class.java)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,17 +49,17 @@ class ReactiveFindOperationExtensionsTests {
verify(operation).query(First::class.java)
}
@Test // DATAMONGO-1719
@Test // DATAMONGO-1719, DATAMONGO-2086
fun `ReactiveFind#FindOperatorWithProjection#asType(KClass) extension should call its Java counterpart`() {
operationWithProjection.asType(First::class)
verify(operationWithProjection).`as`(First::class.java)
operationWithProjection.asType(User::class)
verify(operationWithProjection).`as`(User::class.java)
}
@Test // DATAMONGO-1719
@Test // DATAMONGO-1719, DATAMONGO-2086
fun `ReactiveFind#FindOperatorWithProjection#asType() with reified type parameter extension should call its Java counterpart`() {
operationWithProjection.asType()
verify(operationWithProjection).`as`(First::class.java)
operationWithProjection.asType<User>()
verify(operationWithProjection).`as`(User::class.java)
}
}

View File

@@ -34,7 +34,7 @@ Note that the domain type shown in the preceding example has a property named `i
====
[source]
----
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
public interface PersonRepository extends PagingAndSortingRepository<Person, String> {
// additional custom query methods go here
}

View File

@@ -29,7 +29,7 @@ To create a Spring project in STS:
. Go to File -> New -> Spring Template Project -> Simple Spring Utility Project, and press Yes when prompted. Then enter a project and a package name, such as `org.spring.mongodb.example`.
.Add the following to the pom.xml files `dependencies` element:
+
[source,xml]
[source,xml,subs="+attributes"]
----
<dependencies>
@@ -45,7 +45,7 @@ To create a Spring project in STS:
----
. Change the version of Spring in the pom.xml to be
+
[source,xml]
[source,xml,subs="+attributes"]
----
<spring.framework.version>{springVersion}</spring.framework.version>
----
@@ -1794,6 +1794,7 @@ The MongoDB Aggregation Framework provides the following types of aggregation op
* Array Aggregation Operators
* Conditional Aggregation Operators
* Lookup Aggregation Operators
* Convert Aggregation Operators
At the time of this writing, we provide support for the following Aggregation Operations in Spring Data MongoDB:
@@ -1813,7 +1814,7 @@ At the time of this writing, we provide support for the following Aggregation Op
| `abs`, `add` (*via `plus`), `ceil`, `divide`, `exp`, `floor`, `ln`, `log`, `log10`, `mod`, `multiply`, `pow`, `sqrt`, `subtract` (*via `minus`), `trunc`
| String Aggregation Operators
| `concat`, `substr`, `toLower`, `toUpper`, `stcasecmp`, `indexOfBytes`, `indexOfCP`, `split`, `strLenBytes`, `strLenCP`, `substrCP`
| `concat`, `substr`, `toLower`, `toUpper`, `stcasecmp`, `indexOfBytes`, `indexOfCP`, `split`, `strLenBytes`, `strLenCP`, `substrCP`, `trim`, `ltrim`, `rtim`
| Comparison Aggregation Operators
| `eq` (*via: `is`), `gt`, `gte`, `lt`, `lte`, `ne`
@@ -1836,6 +1837,8 @@ At the time of this writing, we provide support for the following Aggregation Op
| Type Aggregation Operators
| `type`
| Convert Aggregation Operators
| `convert`, `toBool`, `toDate`, `toDecimal`, `toDouble`, `toInt`, `toLong`, `toObjectId`, `toString`
|===
* The operation is mapped or added by Spring Data MongoDB.

View File

@@ -47,7 +47,7 @@ Note that the entity defined in the preceding example has a property named `id`
====
[source]
----
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
Flux<Person> findByFirstname(String firstname); <1>

View File

@@ -25,7 +25,7 @@ To create a Spring project in STS, go to File -> New -> Spring Template Project
Then add the following to the pom.xml dependencies section.
[source,xml]
[source,xml,subs="+attributes"]
----
<dependencies>
@@ -40,7 +40,7 @@ Then add the following to the pom.xml dependencies section.
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-reactivestreams</artifactId>
<version>{mongo.reactivestreams}</version>
<version>{mongo-reactivestreams}</version>
</dependency>
<dependency>

View File

@@ -1,6 +1,172 @@
Spring Data MongoDB Changelog
=============================
Changes in version 2.0.12.RELEASE (2018-11-27)
----------------------------------------------
* DATAMONGO-2135 - Use List instead of Collection when reading data with MappingMongoConverter.
* DATAMONGO-2119 - Combining a SpEL expression and a $regex filter triggers a PatternSyntaxException.
* DATAMONGO-2118 - Fix typo in repositories reference documentation.
* DATAMONGO-2109 - Release 2.0.12 (Kay SR12).
* DATAMONGO-2098 - Typo in MappingMongoConverterParser.
Changes in version 1.10.17.RELEASE (2018-11-27)
-----------------------------------------------
* DATAMONGO-2135 - Use List instead of Collection when reading data with MappingMongoConverter.
* DATAMONGO-2118 - Fix typo in repositories reference documentation.
* DATAMONGO-2110 - Release 1.10.17 (Ingalls SR17).
Changes in version 2.1.2.RELEASE (2018-10-29)
---------------------------------------------
* DATAMONGO-2118 - Fix typo in repositories reference documentation.
* DATAMONGO-2113 - ChangeStreamTasks incorrectly converts resumeAt Instants into BsonTimestamp.
* DATAMONGO-2107 - Release 2.1.2 (Lovelace SR2).
* DATAMONGO-2098 - Typo in MappingMongoConverterParser.
Changes in version 1.10.16.RELEASE (2018-10-15)
-----------------------------------------------
* DATAMONGO-2096 - Aggregation.graphLookup.**.connectFrom(String) does not handle nested field.
* DATAMONGO-2083 - Release 1.10.16 (Ingalls SR16).
Changes in version 2.0.11.RELEASE (2018-10-15)
----------------------------------------------
* DATAMONGO-2101 - NoSuchMethodException: org.springframework.data.mongodb.core.geo.GeoJsonPoint.<init>().
* DATAMONGO-2096 - Aggregation.graphLookup.**.connectFrom(String) does not handle nested field.
* DATAMONGO-2087 - Typo in MongoRepository.
* DATAMONGO-2086 - Kotlin Fluent API extensions do not allow projections with find queries.
* DATAMONGO-2084 - Release 2.0.11 (Kay SR11).
Changes in version 2.1.1.RELEASE (2018-10-15)
---------------------------------------------
* DATAMONGO-2096 - Aggregation.graphLookup.**.connectFrom(String) does not handle nested field.
* DATAMONGO-2094 - Release 2.1.1 (Lovelace SR1).
Changes in version 2.1.0.RELEASE (2018-09-21)
---------------------------------------------
* DATAMONGO-2091 - Upgrade to MongoDB Java Driver 3.8.2 and Reactive Streams Driver 1.9.2.
* DATAMONGO-2090 - Include documentation about Object Mapping Fundamentals.
* DATAMONGO-2087 - Typo in MongoRepository.
* DATAMONGO-2086 - Kotlin Fluent API extensions do not allow projections with find queries.
* DATAMONGO-2080 - DTO projections with reactive @Tailable query methods fail with IllegalArgumentException: Property must not be null.
* DATAMONGO-2078 - Update reference documentation on tailable cursors with the sync MongoDB Java driver.
* DATAMONGO-2076 - Fix property substitution in getting started section of reference docs.
* DATAMONGO-2075 - Open up MongoTransaction manager to allow transaction commit retry..
* DATAMONGO-2069 - Required dependency 'mysema-commons-lang'.
* DATAMONGO-2065 - Make sure that MongoTemplate.doSave(…) calls local populateIdIfNecessary(…) to allow customization.
* DATAMONGO-2064 - Upgrade to MongoDB Java Driver 3.8.1.
* DATAMONGO-2061 - Release 2.1 GA (Lovelace).
Changes in version 2.0.10.RELEASE (2018-09-10)
----------------------------------------------
* DATAMONGO-2076 - Fix property substitution in getting started section of reference docs.
* DATAMONGO-2055 - Allow position modifier to be negative using push at position on Update.
* DATAMONGO-2051 - Add support for SCRAM-SHA-256 authentication mechanism to MongoCredentialPropertyEditor.
* DATAMONGO-2050 - Add support for index selection via key attribute for $geoNear aggregation.
* DATAMONGO-2049 - Add support for MongoDB 4.0 string aggregation operators.
* DATAMONGO-2048 - Add support for MongoDB 4.0 type conversion aggregation operators.
* DATAMONGO-2047 - Update $dateToString and $dateFromString aggregation operators to match MongoDB 4.0 changes.
* DATAMONGO-2046 - Investigate performance regressions between 2.0 GA and 2.1 RC2.
* DATAMONGO-2043 - MappingMongoConverter.write(…) does not consider Document a native type and adds _class attribute.
* DATAMONGO-2034 - Release 2.0.10 (Kay SR10).
* DATAMONGO-2027 - outputCollection and outputType of MapReduceOptions not work.
Changes in version 1.10.15.RELEASE (2018-09-10)
-----------------------------------------------
* DATAMONGO-2076 - Fix property substitution in getting started section of reference docs.
* DATAMONGO-2057 - Guard MongoDbUtils integration tests against MongoDB 4.0 changes.
* DATAMONGO-2055 - Allow position modifier to be negative using push at position on Update.
* DATAMONGO-2051 - Add support for SCRAM-SHA-256 authentication mechanism to MongoCredentialPropertyEditor.
* DATAMONGO-2050 - Add support for index selection via key attribute for $geoNear aggregation.
* DATAMONGO-2049 - Add support for MongoDB 4.0 string aggregation operators.
* DATAMONGO-2048 - Add support for MongoDB 4.0 type conversion aggregation operators.
* DATAMONGO-2047 - Update $dateToString and $dateFromString aggregation operators to match MongoDB 4.0 changes.
* DATAMONGO-2043 - MappingMongoConverter.write(…) does not consider Document a native type and adds _class attribute.
* DATAMONGO-2035 - Release 1.10.15 (Ingalls SR15).
Changes in version 2.1.0.RC2 (2018-08-20)
-----------------------------------------
* DATAMONGO-2055 - Allow position modifier to be negative using push at position on Update.
* DATAMONGO-2053 - Add support for $mergeObjects aggregation operator.
* DATAMONGO-2052 - Add support for MongoDB 3.6 array aggregation operators.
* DATAMONGO-2051 - Add support for SCRAM-SHA-256 authentication mechanism to MongoCredentialPropertyEditor.
* DATAMONGO-2050 - Add support for index selection via key attribute for $geoNear aggregation.
* DATAMONGO-2049 - Add support for MongoDB 4.0 string aggregation operators.
* DATAMONGO-2048 - Add support for MongoDB 4.0 type conversion aggregation operators.
* DATAMONGO-2047 - Update $dateToString and $dateFromString aggregation operators to match MongoDB 4.0 changes.
* DATAMONGO-2046 - Investigate performance regressions between 2.0 GA and 2.1 RC2.
* DATAMONGO-2045 - Add transaction specific error codes for exception translation.
* DATAMONGO-2043 - MappingMongoConverter.write(…) does not consider Document a native type and adds _class attribute.
* DATAMONGO-2041 - Automatically derive the fields to be read from the expected result type.
* DATAMONGO-2040 - Update mongodb docs to reflect Duplicates.DROP is no longer supported.
* DATAMONGO-2033 - Release 2.1 RC2 (Lovelace).
* DATAMONGO-2027 - outputCollection and outputType of MapReduceOptions not work.
Changes in version 1.10.14.RELEASE (2018-07-27)
-----------------------------------------------
* DATAMONGO-2023 - ClassCastException for $sample aggregation operation.
* DATAMONGO-2021 - Use correct document identifier on GridFsOperations getResource method.
* DATAMONGO-2016 - MongoCredentialPropertyEditor throws ArrayIndexOutOfBoundsException when password contains a questionmark.
* DATAMONGO-2006 - Release 1.10.14 (Ingalls SR14).
Changes in version 2.0.9.RELEASE (2018-07-26)
---------------------------------------------
* DATAMONGO-2030 - Reactive repositories don't handle existsByProperty properly.
* DATAMONGO-2029 - String query methods do not convert List<UUID> and List<byte[]> to their correct representation.
* DATAMONGO-2016 - MongoCredentialPropertyEditor throws ArrayIndexOutOfBoundsException when password contains a questionmark.
* DATAMONGO-2011 - Getting exception in MappingMongoConverter while trying to map array of arrays into Object.
* DATAMONGO-2007 - Release 2.0.9 (Kay SR9).
Changes in version 2.1.0.RC1 (2018-07-26)
-----------------------------------------
* DATAMONGO-2030 - Reactive repositories don't handle existsByProperty properly.
* DATAMONGO-2029 - String query methods do not convert List<UUID> and List<byte[]> to their correct representation.
* DATAMONGO-2028 - EntityOperations does not apply conversion to Mongo types.
* DATAMONGO-2026 - Final id field causes UnsupportedOperationException when reading object.
* DATAMONGO-2021 - Use correct document identifier on GridFsOperations getResource method.
* DATAMONGO-2016 - MongoCredentialPropertyEditor throws ArrayIndexOutOfBoundsException when password contains a questionmark.
* DATAMONGO-2012 - Upgrade to MongoDB java driver 3.8 and reactive streams 1.9.
* DATAMONGO-2011 - Getting exception in MappingMongoConverter while trying to map array of arrays into Object.
* DATAMONGO-2010 - SpringDataMongodbSerializer does not convert 'in' predicate for nested String id properties mapping to ObjectId.
* DATAMONGO-2005 - Switch reactive transactions API over to Flux.usingWhen.
* DATAMONGO-2004 - Lazily resolve DBRefs during constructor creation of the enclosing entity.
* DATAMONGO-2003 - MongoQueryCreator will not create query correctly when passing Pattern Object with options.
* DATAMONGO-2002 - Criteria instances using different regex Patterns are considered equal.
* DATAMONGO-2001 - Count within session should return only the total count of documents visible to the specific session.
* DATAMONGO-1998 - SpringDataMongodbSerializer cannot convert query on nested id.
* DATAMONGO-1996 - ChangeStreamEvent Aggregator filter doesn't work for nested fields.
* DATAMONGO-1993 - Change default fullDocumentLookup in ReactiveMongoTemplate.changeStream.
* DATAMONGO-1992 - Add support for immutable objects.
* DATAMONGO-1990 - Adapt build to changes in is-new-handling.
* DATAMONGO-1988 - String ID not working for embeded objects.
* DATAMONGO-1987 - Upgarde test infrastructure to MongoDB 4.0-rc0.
* DATAMONGO-1986 - Aggregation MatchOperation fails to convert query.
* DATAMONGO-1983 - Reference documentation does not include Transaction and sessions.
* DATAMONGO-1982 - Release 2.1 RC1 (Lovelace).
* DATAMONGO-1979 - Adding Support For Sorting in @Query Annotation and Creating an alias for value.
* DATAMONGO-1919 - Decouple reactive mongo bits from blocking MongoClient.
* DATAMONGO-1848 - Migrate to Document API-based Querydsl implementation.
* DATAMONGO-1842 - Optimistic locking allows ReactiveMongoTemplate to modify immutable objects.
* DATAMONGO-1827 - Allow document replacements via MongoCollection.findOneAndReplace(…).
* DATAMONGO-1810 - Querydsl predicate using IN operator fails for DBRef.
* DATAMONGO-1434 - Convert Exceptions into Spring Exception hierarchy when going through Querydsl.
* DATAMONGO-1311 - Add an option to specify the cursor.batchSize() for repository methods returning streams.
* DATAMONGO-1185 - Provide lifecycle events when using QueryDSL.
* DATAMONGO-700 - Events not triggered during Querydsl repository usage.
* DATAMONGO-595 - Expose new QueryDSL elemMatch feature.
* DATAMONGO-362 - QueryDSL does not work with DBRef.
Changes in version 2.0.8.RELEASE (2018-06-13)
---------------------------------------------
* DATAMONGO-2003 - MongoQueryCreator will not create query correctly when passing Pattern Object with options.

View File

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