Compare commits

..

15 Commits

Author SHA1 Message Date
Spring Buildmaster
144000ed46 DATAMONGO-1261 - Release version 1.7.2.RELEASE (Fowler SR2). 2015-07-28 06:44:30 -07:00
Oliver Gierke
9f10921a48 DATAMONGO-1261 - Prepare 1.7.2.RELEASE (Fowler SR2). 2015-07-28 14:52:48 +02:00
Oliver Gierke
c2127760cc DATAMONGO-1261 - Updated changelog. 2015-07-28 14:52:30 +02:00
Christoph Strobl
d2f90bae5d DATAMONGO-1254 - Grouping after projection in aggregation now uses correct aliased field name.
We now push the aliased field name down the aggregation pipeline for projections including operations. This allows to reference them in a later stage. Prior to this change the field reference was potentially resolved to the target field of the operation which did not result in an error but lead to false results.

Original pull request: #311.
2015-07-27 14:17:10 +02:00
Christoph Strobl
b25fde4ff3 DATAMONGO-1260 - Prevent accidental authentication misconfiguration on SimpleMongoDbFactory.
We now reject configuration using MongoClient along with UserCredentials in SimpleMongoDbFactory. This move favors the native authentication mechanism provided via MongoCredential.

<mongo:mongo-client id="mongo-client-with-credentials" credentials="jon:warg@snow?uri.authMechanism=PLAIN" />

Original pull request: #309.
2015-07-27 14:10:40 +02:00
Oliver Gierke
e23ca446a2 DATAMONGO-1257 - We now hint to credential quoting from the XSD.
The namespace XSD now mentions the capability of quoting more complex credentials in case they validly contain a comma.
2015-07-27 13:47:40 +02:00
Oliver Gierke
4c3b1951e2 DATAMONGO-1257 - Polishing.
Made internal helper methods in MongoCredentialPropertyEditor static where possible.

Original pull request: #310.
2015-07-24 18:41:15 +02:00
Christoph Strobl
3474f7e037 DATAMONGO-1257 - <mongo:mongo-client /> element now supports usernames with a comma.
We now allow grouping credentials by enclosing them in single quotes like this:

credentials='CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry?uri.authMechanism=MONGODB-X509'

We also changed the required argument checks to be more authentication mechanism specific which means the pattern is now username[:password@database][?options].

Original pull request: #310.
2015-07-24 18:41:11 +02:00
Christoph Strobl
5308b8f446 DATAMONGO-1251 - Fixed potential NullPointerException in UpdateMapper.
We now explicitly handle the possibility of the source object a type hint needs to be calculated for being null.
2015-07-07 10:07:59 +02:00
Oliver Gierke
e267f329fb DATAMONGO-1250 - Fixed inline code formatting in reference docs. 2015-07-04 19:07:17 +02:00
Oliver Gierke
234009cf53 DATAMONGO-1250 - Fixed accidental duplicate invocation of value conversion in UpdateMapper.
UpdateMapper.getMappedObjectForField(…) invokes the very same method of the super class but handed in an already mapped value so that value conversion was invoked twice.

This was especially problematic in cases a dedicated converter had been registered for an object that is already a Mongo-storable one (e.g. an enum-to-string converter and back) without indicating which of the tow converter is the reading or the writing one. This basically caused the source value converted back and forth during the update mapping creating the impression the value wasn't converted at all.

This is now fixed by removing the superfluous mapping.
2015-07-04 19:00:38 +02:00
Oliver Gierke
ab4fb8477d DATAMONGO-1246 - Updated changelog. 2015-07-01 10:01:08 +02:00
Oliver Gierke
76bec5e42d DATAMONGO-1247 - Updated changelog. 2015-07-01 10:00:55 +02:00
Oliver Gierke
42beaaf47e DATAMONGO-1248 - After release cleanups. 2015-07-01 01:12:28 +02:00
Spring Buildmaster
f32afed779 DATAMONGO-1248 - Prepare next development iteration. 2015-06-30 04:13:25 -07:00
17 changed files with 472 additions and 32 deletions

View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.7.1.RELEASE</version>
<version>1.7.2.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>1.6.1.RELEASE</version>
<version>1.6.2.RELEASE</version>
</parent>
<modules>
@@ -28,7 +28,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>1.10.1.RELEASE</springdata.commons>
<springdata.commons>1.10.2.RELEASE</springdata.commons>
<mongo>2.13.0</mongo>
<mongo.osgi>2.13.0</mongo.osgi>
</properties>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.7.1.RELEASE</version>
<version>1.7.2.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -48,7 +48,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.7.1.RELEASE</version>
<version>1.7.2.RELEASE</version>
</dependency>
<dependency>

View File

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

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.7.1.RELEASE</version>
<version>1.7.2.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>1.7.1.RELEASE</version>
<version>1.7.2.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -17,8 +17,11 @@ package org.springframework.data.mongodb.config;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.StringUtils;
@@ -28,10 +31,13 @@ import com.mongodb.MongoCredential;
* Parse a {@link String} to a Collection of {@link MongoCredential}.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
*/
public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
private static final Pattern GROUP_PATTERN = Pattern.compile("(\\\\?')(.*?)\\1");
private static final String AUTH_MECHANISM_KEY = "uri.authMechanism";
private static final String USERNAME_PASSWORD_DELIMINATOR = ":";
private static final String DATABASE_DELIMINATOR = "@";
@@ -51,11 +57,7 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
for (String credentialString : text.split(",")) {
if (!text.contains(USERNAME_PASSWORD_DELIMINATOR) || !text.contains(DATABASE_DELIMINATOR)) {
throw new IllegalArgumentException("Credentials need to be in format 'username:password@database'!");
}
for (String credentialString : extractCredentialsString(text)) {
String[] userNameAndPassword = extractUserNameAndPassword(credentialString);
String database = extractDB(credentialString);
@@ -68,43 +70,83 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
String authMechanism = options.getProperty(AUTH_MECHANISM_KEY);
if (MongoCredential.GSSAPI_MECHANISM.equals(authMechanism)) {
verifyUserNamePresent(userNameAndPassword);
credentials.add(MongoCredential.createGSSAPICredential(userNameAndPassword[0]));
} else if (MongoCredential.MONGODB_CR_MECHANISM.equals(authMechanism)) {
verifyUsernameAndPasswordPresent(userNameAndPassword);
verifyDatabasePresent(database);
credentials.add(MongoCredential.createMongoCRCredential(userNameAndPassword[0], database,
userNameAndPassword[1].toCharArray()));
} else if (MongoCredential.MONGODB_X509_MECHANISM.equals(authMechanism)) {
verifyUserNamePresent(userNameAndPassword);
credentials.add(MongoCredential.createMongoX509Credential(userNameAndPassword[0]));
} else if (MongoCredential.PLAIN_MECHANISM.equals(authMechanism)) {
verifyUsernameAndPasswordPresent(userNameAndPassword);
verifyDatabasePresent(database);
credentials.add(MongoCredential.createPlainCredential(userNameAndPassword[0], database,
userNameAndPassword[1].toCharArray()));
} else if (MongoCredential.SCRAM_SHA_1_MECHANISM.equals(authMechanism)) {
verifyUsernameAndPasswordPresent(userNameAndPassword);
verifyDatabasePresent(database);
credentials.add(MongoCredential.createScramSha1Credential(userNameAndPassword[0], database,
userNameAndPassword[1].toCharArray()));
} else {
throw new IllegalArgumentException(String.format(
"Cannot create MongoCredentials for unknown auth mechanism '%s'!", authMechanism));
throw new IllegalArgumentException(
String.format("Cannot create MongoCredentials for unknown auth mechanism '%s'!", authMechanism));
}
}
} else {
credentials.add(MongoCredential.createCredential(userNameAndPassword[0], database,
userNameAndPassword[1].toCharArray()));
verifyUsernameAndPasswordPresent(userNameAndPassword);
verifyDatabasePresent(database);
credentials.add(
MongoCredential.createCredential(userNameAndPassword[0], database, userNameAndPassword[1].toCharArray()));
}
}
setValue(credentials);
}
private List<String> extractCredentialsString(String source) {
Matcher matcher = GROUP_PATTERN.matcher(source);
List<String> list = new ArrayList<String>();
while (matcher.find()) {
String value = StringUtils.trimLeadingCharacter(matcher.group(), '\'');
list.add(StringUtils.trimTrailingCharacter(value, '\''));
}
if (!list.isEmpty()) {
return list;
}
return Arrays.asList(source.split(","));
}
private static String[] extractUserNameAndPassword(String text) {
int dbSeperationIndex = text.lastIndexOf(DATABASE_DELIMINATOR);
String userNameAndPassword = text.substring(0, dbSeperationIndex);
return userNameAndPassword.split(USERNAME_PASSWORD_DELIMINATOR);
int index = text.lastIndexOf(DATABASE_DELIMINATOR);
index = index != -1 ? index : text.lastIndexOf(OPTIONS_DELIMINATOR);
return index == -1 ? new String[] {} : text.substring(0, index).split(USERNAME_PASSWORD_DELIMINATOR);
}
private static String extractDB(String text) {
int dbSeperationIndex = text.lastIndexOf(DATABASE_DELIMINATOR);
if (dbSeperationIndex == -1) {
return "";
}
String tmp = text.substring(dbSeperationIndex + 1);
int optionsSeperationIndex = tmp.lastIndexOf(OPTIONS_DELIMINATOR);
@@ -129,4 +171,28 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
return properties;
}
private static void verifyUsernameAndPasswordPresent(String[] source) {
verifyUserNamePresent(source);
if (source.length != 2) {
throw new IllegalArgumentException(
"Credentials need to specify username and password like in 'username:password@database'!");
}
}
private static void verifyDatabasePresent(String source) {
if (!StringUtils.hasText(source)) {
throw new IllegalArgumentException("Credentials need to specify database like in 'username:password@database'!");
}
}
private static void verifyUserNamePresent(String[] source) {
if (source.length == 0 || !StringUtils.hasText(source[0])) {
throw new IllegalArgumentException("Credentials need to specify username!");
}
}
}

View File

@@ -19,6 +19,7 @@ import java.net.UnknownHostException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
@@ -103,8 +104,8 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
*/
@Deprecated
public SimpleMongoDbFactory(MongoURI uri) throws MongoException, UnknownHostException {
this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())),
true, uri.getDatabase());
this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())), true,
uri.getDatabase());
}
/**
@@ -132,6 +133,11 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
private SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials,
boolean mongoInstanceCreated, String authenticationDatabaseName) {
if (mongo instanceof MongoClient && (credentials != null && !UserCredentials.NO_CREDENTIALS.equals(credentials))) {
throw new InvalidDataAccessApiUsageException(
"Usage of 'UserCredentials' with 'MongoClient' is no longer supported. Please use 'MongoCredential' for 'MongoClient' or just 'Mongo'.");
}
Assert.notNull(mongo, "Mongo must not be null");
Assert.hasText(databaseName, "Database name must not be empty");
Assert.isTrue(databaseName.matches("[\\w-]+"),

View File

@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.List;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection;
import org.springframework.util.Assert;
@@ -40,6 +41,7 @@ import com.mongodb.DBObject;
* @author Tobias Trelle
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
* @since 1.3
*/
public class ProjectionOperation implements FieldsExposingAggregationOperation {
@@ -763,6 +765,20 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
return field;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#getExposedField()
*/
@Override
public ExposedField getExposedField() {
if (!getField().isAliased()) {
return super.getExposedField();
}
return new ExposedField(new AggregationField(getField().getName()), true);
}
/**
* Creates a new instance of this {@link OperationProjection} with the given alias.
*

View File

@@ -90,7 +90,7 @@ public class UpdateMapper extends QueryMapper {
return getMappedUpdateModifier(field, rawValue);
}
return super.getMappedObjectForField(field, getMappedValue(field, rawValue));
return super.getMappedObjectForField(field, rawValue);
}
private Entry<String, Object> getMappedUpdateModifier(Field field, Object rawValue) {
@@ -142,19 +142,18 @@ public class UpdateMapper extends QueryMapper {
}
private TypeInformation<?> getTypeHintForEntity(Object source, MongoPersistentEntity<?> entity) {
return processTypeHintForNestedDocuments(source, entity.getTypeInformation());
}
private TypeInformation<?> processTypeHintForNestedDocuments(Object source, TypeInformation<?> info) {
TypeInformation<?> info = entity.getTypeInformation();
Class<?> type = info.getActualType().getType();
if (type.isInterface() || java.lang.reflect.Modifier.isAbstract(type.getModifiers())) {
if (source == null || type.isInterface() || java.lang.reflect.Modifier.isAbstract(type.getModifiers())) {
return info;
}
if (!type.equals(source.getClass())) {
return info;
}
return NESTED_DOCUMENT;
}

View File

@@ -586,7 +586,7 @@ The comma delimited list of host:port entries to use for replica set/pairs.
<xsd:attribute name="credentials" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The comma delimited list of username:password@database entries to use for authentication. Appending ?uri.authMechanism allows to specify the authentication challenge mechanism.
The comma delimited list of username:password@database entries to use for authentication. Appending ?uri.authMechanism allows to specify the authentication challenge mechanism. If the credential you're trying to pass contains a comma itself, quote it with single quotes: '…'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -43,6 +43,9 @@ public class MongoCredentialPropertyEditorUnitTests {
static final String USER_2_PWD = "warg";
static final String USER_2_DB = "snow";
static final String USER_3_NAME = "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry";
static final String USER_3_DB = "stark";
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";
@@ -50,6 +53,9 @@ public class MongoCredentialPropertyEditorUnitTests {
static final String USER_2_AUTH_STRING_WITH_MONGODB_CR_AUTH_MECHANISM = USER_2_AUTH_STRING
+ "?uri.authMechanism=MONGODB-CR";
static final String USER_3_AUTH_STRING_WITH_X509_AUTH_MECHANISM = "'" + USER_3_NAME + "@" + USER_3_DB
+ "?uri.authMechanism=MONGODB-X509'";
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,
@@ -60,6 +66,8 @@ public class MongoCredentialPropertyEditorUnitTests {
static final MongoCredential USER_2_CREDENTIALS_CR_AUTH = MongoCredential.createMongoCRCredential(USER_2_NAME,
USER_2_DB, USER_2_PWD.toCharArray());
static final MongoCredential USER_3_CREDENTIALS_X509_AUTH = MongoCredential.createMongoX509Credential(USER_3_NAME);
MongoCredentialPropertyEditor editor;
@Before
@@ -168,4 +176,75 @@ public class MongoCredentialPropertyEditorUnitTests {
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS));
}
/**
* @see DATAMONGO-1257
*/
@Test
@SuppressWarnings("unchecked")
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() {
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));
}
/**
* @see DATAMONGO-1257
*/
@Test
@SuppressWarnings("unchecked")
public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() {
editor.setAsText("'" + USER_1_AUTH_STRING + "'");
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS));
}
/**
* @see DATAMONGO-1257
*/
@Test
@SuppressWarnings("unchecked")
public void shouldReturnX509CredentialsCorrectly() {
editor.setAsText(USER_3_AUTH_STRING_WITH_X509_AUTH_MECHANISM);
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_3_CREDENTIALS_X509_AUTH));
}
/**
* @see DATAMONGO-1257
*/
@Test
@SuppressWarnings("unchecked")
public void shouldReturnX509CredentialsCorrectlyWhenNoDbSpecified() {
editor.setAsText("tyrion?uri.authMechanism=MONGODB-X509");
assertThat((List<MongoCredential>) editor.getValue(), contains(MongoCredential.createMongoX509Credential("tyrion")));
}
/**
* @see DATAMONGO-1257
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionWhenNoDbSpecifiedForMongodbCR() {
editor.setAsText("tyrion?uri.authMechanism=MONGODB-CR");
editor.getValue();
}
/**
* @see DATAMONGO-1257
*/
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionWhenDbIsEmptyForMongodbCR() {
editor.setAsText("tyrion@?uri.authMechanism=MONGODB-CR");
editor.getValue();
}
}

View File

@@ -22,10 +22,13 @@ import static org.springframework.test.util.ReflectionTestUtils.*;
import java.net.UnknownHostException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
@@ -43,6 +46,7 @@ import com.mongodb.MongoURI;
@RunWith(MockitoJUnitRunner.class)
public class SimpleMongoDbFactoryUnitTests {
public @Rule ExpectedException expectedException = ExpectedException.none();
@Mock Mongo mongo;
/**
@@ -115,6 +119,46 @@ public class SimpleMongoDbFactoryUnitTests {
assertThat(getField(factory, "authenticationDatabaseName").toString(), is("FooBar"));
}
/**
* @see DATAMONGO-1260
*/
@Test
public void rejectsMongoClientWithUserCredentials() {
expectedException.expect(InvalidDataAccessApiUsageException.class);
expectedException.expectMessage("use 'MongoCredential' for 'MongoClient'");
new SimpleMongoDbFactory(mock(MongoClient.class), "cairhienin", new UserCredentials("moiraine", "sedai"));
}
/**
* @see DATAMONGO-1260
*/
@Test
public void rejectsMongoClientWithUserCredentialsAndAuthDb() {
expectedException.expect(InvalidDataAccessApiUsageException.class);
expectedException.expectMessage("use 'MongoCredential' for 'MongoClient'");
new SimpleMongoDbFactory(mock(MongoClient.class), "malkieri", new UserCredentials("lan", "mandragoran"), "authdb");
}
/**
* @see DATAMONGO-1260
*/
@Test
public void shouldNotRejectMongoClientWithNoCredentials() {
new SimpleMongoDbFactory(mock(MongoClient.class), "andoran", UserCredentials.NO_CREDENTIALS);
}
/**
* @see DATAMONGO-1260
*/
@Test
public void shouldNotRejectMongoClientWithEmptyUserCredentials() {
new SimpleMongoDbFactory(mock(MongoClient.class), "shangtai", new UserCredentials("", ""));
}
@SuppressWarnings("deprecation")
private void rejectsDatabaseName(String databaseName) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2015 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.
@@ -30,6 +30,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.data.domain.Sort.Direction;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
/**
@@ -37,6 +38,7 @@ import com.mongodb.DBObject;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
public class AggregationUnitTests {
@@ -283,6 +285,40 @@ public class AggregationUnitTests {
is((DBObject) new BasicDBObject("_id", "$someKey").append("doc", new BasicDBObject("$first", "$$ROOT"))));
}
/**
* @see DATAMONGO-1254
*/
@Test
public void shouldExposeAliasedFieldnameForProjectionsIncludingOperationsDownThePipeline() {
DBObject agg = Aggregation.newAggregation(//
project("date") //
.and("tags").minus(10).as("tags_count")//
, group("date")//
.sum("tags_count").as("count")//
).toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
DBObject group = extractPipelineElement(agg, 1, "$group");
assertThat(getAsDBObject(group, "count"), is(new BasicDBObjectBuilder().add("$sum", "$tags_count").get()));
}
/**
* @see DATAMONGO-1254
*/
@Test
public void shouldUseAliasedFieldnameForProjectionsIncludingOperationsDownThePipelineWhenUsingSpEL() {
DBObject agg = Aggregation.newAggregation(//
project("date") //
.andExpression("tags-10")//
, group("date")//
.sum("tags_count").as("count")//
).toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
DBObject group = extractPipelineElement(agg, 1, "$group");
assertThat(getAsDBObject(group, "count"), is(new BasicDBObjectBuilder().add("$sum", "$tags_count").get()));
}
private DBObject extractPipelineElement(DBObject agg, int index, String operation) {
List<DBObject> pipeline = (List<DBObject>) agg.get("pipeline");

View File

@@ -22,6 +22,7 @@ import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -42,6 +43,9 @@ import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.convert.UpdateMapperUnitTests.ClassWithEnum.Allocation;
import org.springframework.data.mongodb.core.convert.UpdateMapperUnitTests.ClassWithEnum.AllocationToStringConverter;
import org.springframework.data.mongodb.core.convert.UpdateMapperUnitTests.ClassWithEnum.StringToAllocationConverter;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
@@ -762,6 +766,97 @@ public class UpdateMapperUnitTests {
assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteMap.jasnah._class"));
}
/**
* @see DATAMONGO-1250
*/
@Test
@SuppressWarnings("unchecked")
public void mapsUpdateWithBothReadingAndWritingConverterRegistered() {
CustomConversions conversions = new CustomConversions(
Arrays.asList(AllocationToStringConverter.INSTANCE, StringToAllocationConverter.INSTANCE));
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
mappingContext.afterPropertiesSet();
MappingMongoConverter converter = new MappingMongoConverter(mock(DbRefResolver.class), mappingContext);
converter.setCustomConversions(conversions);
converter.afterPropertiesSet();
UpdateMapper mapper = new UpdateMapper(converter);
Update update = new Update().set("allocation", Allocation.AVAILABLE);
DBObject result = mapper.getMappedObject(update.getUpdateObject(),
mappingContext.getPersistentEntity(ClassWithEnum.class));
assertThat(result, isBsonObject().containing("$set.allocation", Allocation.AVAILABLE.code));
}
/**
* see DATAMONGO-1251
*/
@Test
public void mapsNullValueCorrectlyForSimpleTypes() {
Update update = new Update().set("value", null);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ConcreteChildClass.class));
DBObject $set = DBObjectTestUtils.getAsDBObject(mappedUpdate, "$set");
assertThat($set.containsField("value"), is(true));
assertThat($set.get("value"), nullValue());
}
/**
* see DATAMONGO-1251
*/
@Test
public void mapsNullValueCorrectlyForJava8Date() {
Update update = new Update().set("date", null);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ClassWithJava8Date.class));
DBObject $set = DBObjectTestUtils.getAsDBObject(mappedUpdate, "$set");
assertThat($set.containsField("date"), is(true));
assertThat($set.get("value"), nullValue());
}
/**
* see DATAMONGO-1251
*/
@Test
public void mapsNullValueCorrectlyForCollectionTypes() {
Update update = new Update().set("values", null);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ListModel.class));
DBObject $set = DBObjectTestUtils.getAsDBObject(mappedUpdate, "$set");
assertThat($set.containsField("values"), is(true));
assertThat($set.get("value"), nullValue());
}
/**
* see DATAMONGO-1251
*/
@Test
public void mapsNullValueCorrectlyForPropertyOfNestedDocument() {
Update update = new Update().set("concreteValue.name", null);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObject.class));
DBObject $set = DBObjectTestUtils.getAsDBObject(mappedUpdate, "$set");
assertThat($set.containsField("concreteValue.name"), is(true));
assertThat($set.get("concreteValue.name"), nullValue());
}
static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes {
ListModelWrapper concreteTypeWithListAttributeOfInterfaceType;
}
@@ -984,4 +1079,56 @@ public class UpdateMapperUnitTests {
Map<Object, Object> map;
Map<Object, NestedDocument> concreteMap;
}
static class ClassWithEnum {
Allocation allocation;
static enum Allocation {
AVAILABLE("V"), ALLOCATED("A");
String code;
private Allocation(String code) {
this.code = code;
}
public static Allocation of(String code) {
for (Allocation value : values()) {
if (value.code.equals(code)) {
return value;
}
}
throw new IllegalArgumentException();
}
}
static enum AllocationToStringConverter implements Converter<Allocation, String> {
INSTANCE;
@Override
public String convert(Allocation source) {
return source.code;
}
}
static enum StringToAllocationConverter implements Converter<String, Allocation> {
INSTANCE;
@Override
public Allocation convert(String source) {
return Allocation.of(source);
}
}
}
static class ClassWithJava8Date {
LocalDate date;
}
}

View File

@@ -2013,7 +2013,7 @@ class MyConverter implements Converter<Person, String> { … }
class MyConverter implements Converter<String, Person> { … }
----
In case you write a `Converter` whose source and target type are native Mongo types there's no way for us to determine whether we should consider it as reading or writing converter. Registering the converter instance as both might lead to unwanted results then. E.g. a `Converter<String, Long>` is ambiguous although it probably does not make sense to try to convert all `String`s into `Long`s when writing. To be generally able to force the infrastructure to register a converter for one way only we provide `@ReadingConverter` as well as `@WritingConverter` to be used at the converter implementation.
In case you write a `Converter` whose source and target type are native Mongo types there's no way for us to determine whether we should consider it as reading or writing converter. Registering the converter instance as both might lead to unwanted results then. E.g. a `Converter<String, Long>` is ambiguous although it probably does not make sense to try to convert all `String` instances into `Long` instances when writing. To be generally able to force the infrastructure to register a converter for one way only we provide `@ReadingConverter` as well as `@WritingConverter` to be used at the converter implementation.
[[mongo-template.index-and-collections]]
== Index and Collection management

View File

@@ -1,6 +1,53 @@
Spring Data MongoDB Changelog
=============================
Changes in version 1.7.2.RELEASE (2015-07-28)
---------------------------------------------
* DATAMONGO-1261 - Release 1.7.2 (Fowler).
* DATAMONGO-1260 - Prevent accidental authentication misconfiguration on SimpleMongoDbFactory.
* DATAMONGO-1257 - mongo:mongo-client namespace does not support usernames with a comma.
* DATAMONGO-1254 - Group after Project in aggregation uses incorrect field name.
* DATAMONGO-1251 - update / findAndModify throws NullPointerException.
* DATAMONGO-1250 - Custom converter implementation not used in updates.
Changes in version 1.5.6.RELEASE (2015-07-01)
---------------------------------------------
* DATAMONGO-1246 - Release 1.5.6 (Dijkstra).
* DATAMONGO-1234 - Fix typos in JavaDoc.
* DATAMONGO-1232 - IgnoreCase should escape queries.
* DATAMONGO-1229 - MongoQueryCreator incorrectly rejects ignoreCase on nested String path.
* DATAMONGO-1224 - Assert Spring Framework 4.2 compatibility.
* DATAMONGO-1221 - Remove relative reference to parent POM to make sure the right Spring version is picked up.
* DATAMONGO-1207 - MongoTemplate#doInsertAll throws NullPointerException when passed Collection contains a null item.
* DATAMONGO-1180 - Incorrect exception message creation in PartTreeMongoQuery.
* DATAMONGO-1166 - ReadPreference not used for Aggregations.
* DATAMONGO-1155 - Upgrade mongo-next build profiles to Java driver version 2.13.0.
Changes in version 1.6.3.RELEASE (2015-07-01)
---------------------------------------------
* DATAMONGO-1247 - Release 1.6.3 (Evans).
* DATAMONGO-1242 - Update mongo-java-driver to 3.0.2 in mongo3 profile.
* DATAMONGO-1234 - Fix typos in JavaDoc.
* DATAMONGO-1232 - IgnoreCase should escape queries.
* DATAMONGO-1229 - MongoQueryCreator incorrectly rejects ignoreCase on nested String path.
* DATAMONGO-1224 - Assert Spring Framework 4.2 compatibility.
* DATAMONGO-1221 - Remove relative reference to parent POM to make sure the right Spring version is picked up.
* DATAMONGO-1213 - Include new section on Spring Data and Spring Framework dependencies in reference documentation.
* DATAMONGO-1210 - Inconsistent property order of _class type hint breaks document equality.
* DATAMONGO-1207 - MongoTemplate#doInsertAll throws NullPointerException when passed Collection contains a null item.
* DATAMONGO-1196 - Upgrade build profiles after MongoDB 3.0 Java driver release.
* DATAMONGO-1180 - Incorrect exception message creation in PartTreeMongoQuery.
* DATAMONGO-1166 - ReadPreference not used for Aggregations.
* DATAMONGO-1157 - Throw meaningful exception when @DbRef is used with unsupported types.
* DATAMONGO-1155 - Upgrade mongo-next build profiles to Java driver version 2.13.0.
* DATAMONGO-1153 - Fix documentation build.
* DATAMONGO-1133 - Field aliasing is not honored in Aggregation operations.
* DATAMONGO-1124 - Switch log level for cyclic reference index warnings from WARN to INFO.
* DATAMONGO-1081 - Improve documentation on field mapping semantics.
Changes in version 1.7.1.RELEASE (2015-06-30)
---------------------------------------------
* DATAMONGO-1248 - Release 1.7.1 (Fowler).

View File

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