Compare commits

...

42 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
Spring Buildmaster
b442abeef9 DATAMONGO-1248 - Release version 1.7.1.RELEASE (Fowler SR1). 2015-06-30 04:13:22 -07:00
Oliver Gierke
377c421052 DATAMONGO-1248 - Prepare 1.7.1.RELEASE (Fowler SR1). 2015-06-30 12:55:12 +02:00
Oliver Gierke
fdef631c6e DATAMONGO-1248 - Updated changelog. 2015-06-30 12:31:33 +02:00
Oliver Gierke
7ec61c9360 DATAMONGO-1228 - Updated changelog. 2015-06-30 12:04:32 +02:00
Oliver Gierke
a95b9577da DATAMONGO-1236 - Polishing.
Removed the creation of a BasicMongoPersistentEntity in favor of always handing ClassTypeInformation.OBJECT into the converter in case not entity can be found.

This makes sure type information is written for updates on properties of type Object (which essentially leads to no PersistentEntity being available).

Original pull request: #301.
2015-06-30 09:55:32 +02:00
Christoph Strobl
a02fe28644 DATAMONGO-1236 - Update now include type hint correctly.
We now use property type information when mapping fields affected by an update in case we do not have proper entity information within the context. This allows more precise type resolution required for determining the need to write type hints for a given property.

Original pull request: #301.
2015-06-30 09:55:29 +02:00
Christoph Strobl
d620546a3e DATAMONGO-1232 - IngoreCase in criteria now escapes query.
We now quote the original criteria before actually wrapping it inside of an regular expression for case insensitive search. This happens not only to case insensitive is, startsWith, endsWith criteria but also to those using like. In that case we quote the part between leading and trailing wildcard if required.

Original pull request: #301.
2015-06-22 12:50:17 +02:00
Christoph Strobl
f1621364b0 DATAMONGO-1166 - ReadPreference is now be used for aggregations.
We now use MongoTemplate.readPreference(…) when executing commands such as geoNear(…) and aggregate(…).

Original pull request: #303.
2015-06-22 08:20:53 +02:00
Christoph Strobl
02578a0168 DATAMONGO-1157 - Throw meaningful exception when @DbRef is used with unsupported types.
We now eagerly check DBRef properties for invalid definitions such as final class or array. In that case we throw a MappingException when verify is called.
2015-06-19 15:54:26 +02:00
Thomas Darimont
4bab7b32f2 DATAMONGO-1242 - Update MongoDB Java driver to 3.0.2 in mongo3 profile.
Update mongo driver.

Original pull request: #304.
2015-06-19 15:37:55 +02:00
Oliver Gierke
0bda096e61 DATAMONGO-1229 - Fixed application of ignore case flag on nested properties.
Previously we tried to apply the ignore case settings found in the PartTree to the root PropertyPath we handle in MongoQueryCreator.create(). This is now changed to work on the leaf property of the PropertyPath.
2015-06-05 06:49:13 +02:00
Eddú Meléndez
17139dcf3b DATAMONGO-1234 - Fix typos in JavaDoc. 2015-06-05 06:38:04 +02:00
Oliver Gierke
bebfea0d33 DATAMONGO-1210 - Polishing.
Moved getTypeHint(…) method to Field class.

Original pull request: #292.
2015-06-01 13:23:14 +02:00
Christoph Strobl
885f0f4b70 DATAMONGO-1210 - Fixed type hints for usage with findAndModify(…).
We now inspect the actual field type during update mapping and provide a type hint accordingly. Simple, non interface and non abstract types will no longer be decorated with the _class attribute. We now honor positional parameters when trying to map paths to properties. This allows more decent type mapping since we have now access to the meta model which allows us to check if presence of type hint (aka _class) is required.

We now add a special type hint indicating nested types to the converter. This allows more fine grained removal of _class property without the need to break the contract of MongoWriter.convertToMongoType(…).

Original pull request: #292.
2015-06-01 13:23:05 +02:00
Stefan Ganzer
a6000ee771 DATAMONGO-1210 - Add breaking test case for findAndModify/addToSet/each.
The problem stems from the inconsistent handling of type hints such as MongoTemplate.save(…) does not add a type hint, but findAndModify(…) does. The same values are then treated differently by MongoDB, depending on whether they have a type hint or not. To verify this behavior, you can manually add the (superfluous) type hint to the saved object - findAndModify will then work as expected.

Additional tests demonstrate that findAndModify(…) removes type hints from complex documents in collections that are either nested in another collection or in a document, or doesn't add them in the first place.

Original pull requests: #290, #291.
Related pull request: #292.
CLA: 119820150506013701 (Stefan Ganzer)
2015-06-01 13:22:58 +02:00
Christoph Strobl
d4792cd680 DATAMONGO-1216 - Skip authentication via AuthDB for MongoClient.
We now skip authentication via an explicit AuthDB when requesting a DB via a MongoClient instance.

Related ticket: DATACMNS-1218
Original pull request: #296.
2015-06-01 12:13:14 +02:00
Christoph Strobl
949833a7db DATAMONGO-1202 - Polishing.
Moved and renamed types into test class.
Added collection cleanup and missing author information.

Original pull request: #293.
2015-06-01 09:26:06 +02:00
Thomas Darimont
5abf1147e4 DATAMONGO-1202 - More robust type inspection for @Indexed properties.
We now use TypeInformation in IndexResolver to lookup the root PersistentEntity for resolving @Indexed properties to ensure that we retrieve the same PersistentEntity that was stored. Previously we used the Class to lookup up the PersistentEntity which yielded a partially processed result.

Original pull request: #293.
2015-06-01 09:11:16 +02:00
Christoph Strobl
d65be17338 DATAMONGO-1193 - Prevent unnecessary database lookups when resolving DBRefs on 2.x driver.
We now check against the used driver version before requesting db instance from factory. Potential improvements on fetch strategy for MongoDB Java Driver 3 will be handled in DATAMONGO-1194.

Related tickets: DATAMONGO-1194.
Original pull request: #286.
2015-06-01 08:12:44 +02:00
Oliver Gierke
d1f915f702 DATAMONGO-1224 - Ensure Spring Framework 4.2 compatibility.
Removed obsolete generics in MongoPersistentEntityIndexCreator to make sure MappingContextEvents are delivered to the listener on Spring 4.2 which applies more strict generics handling to ApplicationEvents.

Tweaked PersonBeforeSaveListener in test code to actually reflect how an ApplicationEventListener for MongoDB would be implemented.

Removed deprecated (and now removed) usage of ConversionServiceFactory in AbstractMongoConverter. Added MongoMappingEventPublisher.publishEvent(Object) as NoOp.
2015-05-25 13:12:57 +02:00
Domenique Tilleuil
58d50aa1ff DATAMONGO-1208 - Use QueryCursorPreparer for streaming in MongoTemplate.
We now use the QueryCursorPreparer honor skip, limit, sort, etc. for streaming.

Original pull request: #297.
Polishing pull request: #298.
2015-05-21 09:00:57 +02:00
Oliver Gierke
0f30ffa090 DATAMONGO-1221 - Removed <relativePath /> element from parent POM declaration. 2015-05-15 15:07:57 +02:00
Oliver Gierke
496331d755 DATAMONGO-1213 - Included section on dependency management in reference documentation.
Related ticket: DATACMNS-687.
2015-05-04 14:51:55 +02:00
Oliver Gierke
7658e1f1d3 DATAMONGO-1207 - Fixed potential NPE in MongoTemplate.doInsertAll(…).
If a collection containing null values is handed to MongoTempalte.insertAll(…), a NullPointerException was caused by the unguarded attempt to lookup the class of the element. We now explicitly handle this case and skip the element.

Some code cleanups in MongoTemplate.doInsertAll(…).
2015-05-02 14:48:49 +02:00
Oliver Gierke
221e03947b DATAMONGO-1196 - Upgraded build profiles after MongoDB 3.0 Java driver GA release. 2015-04-01 17:14:25 +02:00
Oliver Gierke
f0aed498d5 DATAMONGO-1189 - After release cleanups. 2015-03-30 22:08:35 +02:00
Spring Buildmaster
a62f6b8043 DATAMONGO-1189 - Prepare next development iteration. 2015-03-30 22:06:58 +02:00
50 changed files with 1886 additions and 185 deletions

11
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.7.0.RELEASE</version>
<version>1.7.2.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,8 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>1.6.0.RELEASE</version>
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
<version>1.6.2.RELEASE</version>
</parent>
<modules>
@@ -29,7 +28,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>1.10.0.RELEASE</springdata.commons>
<springdata.commons>1.10.2.RELEASE</springdata.commons>
<mongo>2.13.0</mongo>
<mongo.osgi>2.13.0</mongo.osgi>
</properties>
@@ -108,7 +107,7 @@
<id>mongo-next</id>
<properties>
<mongo>2.13.0-SNAPSHOT</mongo>
<mongo>2.14.0-SNAPSHOT</mongo>
</properties>
<repositories>
@@ -124,7 +123,7 @@
<id>mongo3</id>
<properties>
<mongo>3.0.0-beta3</mongo>
<mongo>3.0.2</mongo>
</properties>
</profile>

View File

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

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.7.0.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.0.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

@@ -25,7 +25,7 @@ import com.mongodb.DBCursor;
interface CursorPreparer {
/**
* Prepare the given cursor (apply limits, skips and so on). Returns th eprepared cursor.
* Prepare the given cursor (apply limits, skips and so on). Returns the prepared cursor.
*
* @param cursor
*/

View File

@@ -49,7 +49,7 @@ public class MongoAction {
* @param collectionName the collection name, must not be {@literal null} or empty.
* @param entityType the POJO that is being operated against
* @param document the converted DBObject from the POJO or Spring Update object
* @param query the converted DBOjbect from the Spring Query object
* @param query the converted DBObject from the Spring Query object
*/
public MongoAction(WriteConcern defaultWriteConcern, MongoActionOperation mongoActionOperation,
String collectionName, Class<?> entityType, DBObject document, DBObject query) {

View File

@@ -123,7 +123,7 @@ public abstract class MongoDbUtils {
DB db = mongo.getDB(databaseName);
if (requiresAuthDbAuthentication(credentials)) {
if (!(mongo instanceof MongoClient) && requiresAuthDbAuthentication(credentials)) {
ReflectiveDbInvoker.authenticate(mongo, db, credentials, authenticationDatabaseName);
}
@@ -199,8 +199,8 @@ public abstract class MongoDbUtils {
}
/**
* Check if credentials present. In case we're using a monog-java-driver version 3 or above we do not have the need
* for authentication as the auth data has to be provied within the MongoClient
* Check if credentials present. In case we're using a mongo-java-driver version 3 or above we do not have the need
* for authentication as the auth data has to be provided within the MongoClient
*
* @param credentials
* @return

View File

@@ -136,6 +136,7 @@ import com.mongodb.util.JSONParseException;
* @author Thomas Darimont
* @author Chuong Ngo
* @author Christoph Strobl
* @author Doménique Tilleuil
*/
@SuppressWarnings("deprecation")
public class MongoTemplate implements MongoOperations, ApplicationContextAware {
@@ -335,9 +336,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity);
DBCursor cursor = collection.find(mappedQuery, mappedFields);
QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType);
ReadDbObjectCallback<T> readCallback = new ReadDbObjectCallback<T>(mongoConverter, entityType);
return new CloseableIterableCusorAdapter<T>(cursor, exceptionTranslator, readCallback);
return new CloseableIterableCusorAdapter<T>(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
}
});
}
@@ -382,7 +385,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
CommandResult result = execute(new DbCallback<CommandResult>() {
public CommandResult doInDB(DB db) throws MongoException, DataAccessException {
return db.command(command, readPreference);
return readPreference != null ? db.command(command, readPreference) : db.command(command);
}
});
@@ -629,7 +632,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
BasicDBObject command = new BasicDBObject("geoNear", collection);
command.putAll(near.toDBObject());
CommandResult commandResult = executeCommand(command);
CommandResult commandResult = executeCommand(command, this.readPreference);
List<Object> results = (List<Object>) commandResult.get("results");
results = results == null ? Collections.emptyList() : results;
@@ -841,27 +844,33 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
protected <T> void doInsertAll(Collection<? extends T> listToSave, MongoWriter<T> writer) {
Map<String, List<T>> objs = new HashMap<String, List<T>>();
for (T o : listToSave) {
Map<String, List<T>> elementsByCollection = new HashMap<String, List<T>>();
for (T element : listToSave) {
if (element == null) {
continue;
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(element.getClass());
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(o.getClass());
if (entity == null) {
throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class "
+ o.getClass().getName());
throw new InvalidDataAccessApiUsageException("No PersistentEntity information found for " + element.getClass());
}
String collection = entity.getCollection();
List<T> collectionElements = elementsByCollection.get(collection);
List<T> objList = objs.get(collection);
if (null == objList) {
objList = new ArrayList<T>();
objs.put(collection, objList);
if (null == collectionElements) {
collectionElements = new ArrayList<T>();
elementsByCollection.put(collection, collectionElements);
}
objList.add(o);
collectionElements.add(element);
}
for (Map.Entry<String, List<T>> entry : objs.entrySet()) {
for (Map.Entry<String, List<T>> entry : elementsByCollection.entrySet()) {
doInsertBatch(entry.getKey(), entry.getValue(), this.mongoConverter);
}
}
@@ -1494,7 +1503,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command));
}
CommandResult commandResult = executeCommand(command);
CommandResult commandResult = executeCommand(command, this.readPreference);
handleCommandError(commandResult, command);
return new AggregationResults<O>(returnPotentiallyMappedResults(outputType, commandResult), commandResult);

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

@@ -88,7 +88,7 @@ public final class ExposedFields implements Iterable<ExposedField> {
}
/**
* Creates a new {@link ExposedFields} instance for the given fields in either sythetic or non-synthetic way.
* Creates a new {@link ExposedFields} instance for the given fields in either synthetic or non-synthetic way.
*
* @param fields must not be {@literal null}.
* @param synthetic
@@ -107,7 +107,7 @@ public final class ExposedFields implements Iterable<ExposedField> {
}
/**
* Creates a new {@link ExposedFields} with the given orignals and synthetics.
* Creates a new {@link ExposedFields} with the given originals and synthetics.
*
* @param originals must not be {@literal null}.
* @param synthetic must not be {@literal null}.
@@ -363,7 +363,7 @@ public final class ExposedFields implements Iterable<ExposedField> {
}
/**
* Returns the referenve value for the given field reference. Will return 1 for a synthetic, unaliased field or the
* Returns the reference value for the given field reference. Will return 1 for a synthetic, unaliased field or the
* raw rendering of the reference otherwise.
*
* @return

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

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-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.
@@ -20,7 +20,7 @@ import java.math.BigInteger;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.mongodb.core.convert.MongoConverters.BigIntegerToObjectIdConverter;
@@ -46,10 +46,8 @@ public abstract class AbstractMongoConverter implements MongoConverter, Initiali
*
* @param conversionService
*/
@SuppressWarnings("deprecation")
public AbstractMongoConverter(GenericConversionService conversionService) {
this.conversionService = conversionService == null ? ConversionServiceFactory.createDefaultConversionService()
: conversionService;
this.conversionService = conversionService == null ? new DefaultConversionService() : conversionService;
}
/**

View File

@@ -106,7 +106,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
*/
@Override
public DBObject fetch(DBRef dbRef) {
return ReflectiveDBRefResolver.fetch(mongoDbFactory.getDb(), dbRef);
return ReflectiveDBRefResolver.fetch(mongoDbFactory, dbRef);
}
/**

View File

@@ -136,8 +136,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param typeMapper the typeMapper to set
*/
public void setTypeMapper(MongoTypeMapper typeMapper) {
this.typeMapper = typeMapper == null ? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY,
mappingContext) : typeMapper;
this.typeMapper = typeMapper == null
? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext) : typeMapper;
}
/*
@@ -238,7 +238,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<MongoPersistentProperty>(
entity, provider, path.getCurrentObject());
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider, path);
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider,
path);
}
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final DBObject dbo, final ObjectPath path) {
@@ -510,8 +511,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
: new BasicDBObject();
addCustomTypeKeyIfNecessary(ClassTypeInformation.from(prop.getRawType()), obj, propDbObj);
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass()) ? mappingContext
.getPersistentEntity(obj.getClass()) : mappingContext.getPersistentEntity(type);
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass())
? mappingContext.getPersistentEntity(obj.getClass()) : mappingContext.getPersistentEntity(type);
writeInternal(obj, propDbObj, entity);
accessor.put(prop, propDbObj);
@@ -700,8 +701,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (mapKeyDotReplacement == null) {
throw new MappingException(String.format("Map key %s contains dots but no replacement was configured! Make "
+ "sure map keys don't contain dots in the first place or configure an appropriate replacement!", source));
throw new MappingException(String.format(
"Map key %s contains dots but no replacement was configured! Make "
+ "sure map keys don't contain dots in the first place or configure an appropriate replacement!",
source));
}
return source.replaceAll("\\.", mapKeyDotReplacement);
@@ -719,8 +722,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return (String) key;
}
return conversions.hasCustomWriteTarget(key.getClass(), String.class) ? (String) getPotentiallyConvertedSimpleWrite(key)
: key.toString();
return conversions.hasCustomWriteTarget(key.getClass(), String.class)
? (String) getPotentiallyConvertedSimpleWrite(key) : key.toString();
}
/**
@@ -889,16 +892,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Class<?> rawComponentType = componentType == null ? null : componentType.getType();
collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<Object>() : CollectionFactory
.createCollection(collectionType, rawComponentType, sourceValue.size());
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<Object>()
: CollectionFactory.createCollection(collectionType, rawComponentType, sourceValue.size());
for (int i = 0; i < sourceValue.size(); i++) {
Object dbObjItem = sourceValue.get(i);
if (dbObjItem instanceof DBRef) {
items.add(DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem),
path));
items.add(
DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem), path));
} else if (dbObjItem instanceof DBObject) {
items.add(read(componentType, (DBObject) dbObjItem, path));
} else {
@@ -1016,10 +1019,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this.write(obj, newDbo);
if (typeInformation == null) {
return removeTypeInfoRecursively(newDbo);
return removeTypeInfo(newDbo, true);
}
return !obj.getClass().equals(typeInformation.getType()) ? newDbo : removeTypeInfoRecursively(newDbo);
if (typeInformation.getType().equals(NestedDocument.class)) {
return removeTypeInfo(newDbo, false);
}
return !obj.getClass().equals(typeInformation.getType()) ? newDbo : removeTypeInfo(newDbo, true);
}
public BasicDBList maybeConvertList(Iterable<?> source, TypeInformation<?> typeInformation) {
@@ -1033,12 +1040,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
/**
* Removes the type information from the conversion result.
* Removes the type information from the entire conversion result.
*
* @param object
* @param recursively whether to apply the removal recursively
* @return
*/
private Object removeTypeInfoRecursively(Object object) {
private Object removeTypeInfo(Object object, boolean recursively) {
if (!(object instanceof DBObject)) {
return object;
@@ -1046,19 +1054,29 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
DBObject dbObject = (DBObject) object;
String keyToRemove = null;
for (String key : dbObject.keySet()) {
if (typeMapper.isTypeKey(key)) {
keyToRemove = key;
if (recursively) {
Object value = dbObject.get(key);
if (value instanceof BasicDBList) {
for (Object element : (BasicDBList) value) {
removeTypeInfo(element, recursively);
}
} else {
removeTypeInfo(value, recursively);
}
}
Object value = dbObject.get(key);
if (value instanceof BasicDBList) {
for (Object element : (BasicDBList) value) {
removeTypeInfoRecursively(element);
if (typeMapper.isTypeKey(key)) {
keyToRemove = key;
if (!recursively) {
break;
}
} else {
removeTypeInfoRecursively(value);
}
}
@@ -1122,8 +1140,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
*
* @author Oliver Gierke
*/
private class ConverterAwareSpELExpressionParameterValueProvider extends
SpELExpressionParameterValueProvider<MongoPersistentProperty> {
private class ConverterAwareSpELExpressionParameterValueProvider
extends SpELExpressionParameterValueProvider<MongoPersistentProperty> {
private final ObjectPath path;
@@ -1135,7 +1153,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param delegate must not be {@literal null}.
*/
public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator,
ConversionService conversionService, ParameterValueProvider<MongoPersistentProperty> delegate, ObjectPath path) {
ConversionService conversionService, ParameterValueProvider<MongoPersistentProperty> delegate,
ObjectPath path) {
super(evaluator, conversionService, delegate);
this.path = path;
@@ -1194,4 +1213,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
DBObject readRef(DBRef ref) {
return dbRefResolver.fetch(ref);
}
/**
* 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.
*
* @author Christoph Strobl
* @since 1.8
*/
static class NestedDocument {
}
}

View File

@@ -34,10 +34,13 @@ import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter.NestedDocument;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import com.mongodb.BasicDBList;
@@ -58,6 +61,7 @@ public class QueryMapper {
private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
private static final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore");
static final ClassTypeInformation<?> NESTED_DOCUMENT = ClassTypeInformation.from(NestedDocument.class);
private enum MetaMapping {
FORCE, WHEN_PRESENT, IGNORE;
@@ -250,8 +254,8 @@ public class QueryMapper {
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists();
Object value = keyword.getValue();
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property) : getMappedValue(
property.with(keyword.getKey()), value);
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property)
: getMappedValue(property.with(keyword.getKey()), value);
return new BasicDBObject(keyword.key, convertedValue);
}
@@ -473,8 +477,8 @@ public class QueryMapper {
}
try {
return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService
.convert(id, ObjectId.class) : delegateConvertToMongoType(id, null);
return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService.convert(id, ObjectId.class)
: delegateConvertToMongoType(id, null);
} catch (ConversionException o_O) {
return delegateConvertToMongoType(id, null);
}
@@ -657,6 +661,10 @@ public class QueryMapper {
public Association<MongoPersistentProperty> getAssociation() {
return null;
}
public TypeInformation<?> getTypeHint() {
return ClassTypeInformation.OBJECT;
}
}
/**
@@ -816,7 +824,7 @@ public class QueryMapper {
try {
PropertyPath path = PropertyPath.from(pathExpression, entity.getTypeInformation());
PropertyPath path = PropertyPath.from(pathExpression.replaceAll("\\.\\d", ""), entity.getTypeInformation());
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
@@ -862,6 +870,27 @@ public class QueryMapper {
protected Converter<MongoPersistentProperty, String> getAssociationConverter() {
return new AssociationConverter(getAssociation());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#getTypeHint()
*/
@Override
public TypeInformation<?> getTypeHint() {
MongoPersistentProperty property = getProperty();
if (property == null) {
return super.getTypeHint();
}
if (property.getActualType().isInterface()
|| java.lang.reflect.Modifier.isAbstract(property.getActualType().getModifiers())) {
return ClassTypeInformation.OBJECT;
}
return NESTED_DOCUMENT;
}
}
/**

View File

@@ -20,9 +20,9 @@ import static org.springframework.util.ReflectionUtils.*;
import java.lang.reflect.Method;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.util.Assert;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
@@ -51,12 +51,14 @@ class ReflectiveDBRefResolver {
* @param ref must not be {@literal null}.
* @return the document that this references.
*/
public static DBObject fetch(DB db, DBRef ref) {
public static DBObject fetch(MongoDbFactory factory, DBRef ref) {
Assert.notNull(ref, "DBRef to fetch must not be null!");
if (isMongo3Driver()) {
return db.getCollection(ref.getCollectionName()).findOne(ref.getId());
Assert.notNull(factory, "DbFactory to fetch DB from must not be null!");
return factory.getDb().getCollection(ref.getCollectionName()).findOne(ref.getId());
}
return (DBObject) invokeMethod(FETCH_METHOD, ref);

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.
@@ -29,6 +29,7 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update.Modifier;
import org.springframework.data.mongodb.core.query.Update.Modifiers;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
@@ -65,8 +66,8 @@ public class UpdateMapper extends QueryMapper {
*/
@Override
protected Object delegateConvertToMongoType(Object source, MongoPersistentEntity<?> entity) {
return entity == null ? super.delegateConvertToMongoType(source, null) : converter.convertToMongoType(source,
entity.getTypeInformation());
return converter.convertToMongoType(source,
entity == null ? ClassTypeInformation.OBJECT : getTypeHintForEntity(source, entity));
}
/*
@@ -89,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) {
@@ -97,14 +98,14 @@ public class UpdateMapper extends QueryMapper {
if (rawValue instanceof Modifier) {
value = getMappedValue((Modifier) rawValue);
value = getMappedValue(field, (Modifier) rawValue);
} else if (rawValue instanceof Modifiers) {
DBObject modificationOperations = new BasicDBObject();
for (Modifier modifier : ((Modifiers) rawValue).getModifiers()) {
modificationOperations.putAll(getMappedValue(modifier).toMap());
modificationOperations.putAll(getMappedValue(field, modifier).toMap());
}
value = modificationOperations;
@@ -132,12 +133,30 @@ public class UpdateMapper extends QueryMapper {
return value instanceof Query;
}
private DBObject getMappedValue(Modifier modifier) {
private DBObject getMappedValue(Field field, Modifier modifier) {
Object value = converter.convertToMongoType(modifier.getValue(), ClassTypeInformation.OBJECT);
TypeInformation<?> typeHint = field == null ? ClassTypeInformation.OBJECT : field.getTypeHint();
Object value = converter.convertToMongoType(modifier.getValue(), typeHint);
return new BasicDBObject(modifier.getKey(), value);
}
private TypeInformation<?> getTypeHintForEntity(Object source, MongoPersistentEntity<?> entity) {
TypeInformation<?> info = entity.getTypeInformation();
Class<?> type = info.getActualType().getType();
if (source == null || type.isInterface() || java.lang.reflect.Modifier.isAbstract(type.getModifiers())) {
return info;
}
if (!type.equals(source.getClass())) {
return info;
}
return NESTED_DOCUMENT;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper#createPropertyField(org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.String, org.springframework.data.mapping.context.MappingContext)
@@ -146,8 +165,8 @@ public class UpdateMapper extends QueryMapper {
protected Field createPropertyField(MongoPersistentEntity<?> entity, String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
return entity == null ? super.createPropertyField(entity, key, mappingContext) : //
new MetadataBackedUpdateField(entity, key, mappingContext);
return entity == null ? super.createPropertyField(entity, key, mappingContext)
: new MetadataBackedUpdateField(entity, key, mappingContext);
}
/**
@@ -233,7 +252,35 @@ public class UpdateMapper extends QueryMapper {
protected String mapPropertyName(MongoPersistentProperty property) {
String mappedName = PropertyToFieldNameConverter.INSTANCE.convert(property);
return iterator.hasNext() && iterator.next().equals("$") ? String.format("%s.$", mappedName) : mappedName;
boolean inspect = iterator.hasNext();
while (inspect) {
String partial = iterator.next();
boolean isPositional = isPositionalParameter(partial);
if (isPositional) {
mappedName += "." + partial;
}
inspect = isPositional && iterator.hasNext();
}
return mappedName;
}
boolean isPositionalParameter(String partial) {
if (partial.equals("$")) {
return true;
}
try {
Long.valueOf(partial);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-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.
@@ -16,22 +16,24 @@
package org.springframework.data.mongodb.core.index;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
import org.springframework.data.util.TypeInformation;
/**
* {@link IndexResolver} finds those {@link IndexDefinition}s to be created for a given class.
*
* @author Christoph Strobl
* @author Thomas Darimont
* @since 1.5
*/
interface IndexResolver {
/**
* Find and create {@link IndexDefinition}s for properties of given {@code type}. {@link IndexDefinition}s are created
* Find and create {@link IndexDefinition}s for properties of given {@link TypeInformation}. {@link IndexDefinition}s are created
* for properties and types with {@link Indexed}, {@link CompoundIndexes} or {@link GeoSpatialIndexed}.
*
* @param type
* @param typeInformation
* @return Empty {@link Iterable} in case no {@link IndexDefinition} could be resolved for type.
*/
Iterable<? extends IndexDefinitionHolder> resolveIndexForClass(Class<?> type);
Iterable<? extends IndexDefinitionHolder> resolveIndexFor(TypeInformation<?> typeInformation);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-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.
@@ -60,4 +60,10 @@ public class MongoMappingEventPublisher implements ApplicationEventPublisher {
indexCreator.onApplicationEvent((MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>) event);
}
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationEventPublisher#publishEvent(java.lang.Object)
*/
public void publishEvent(Object event) {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-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.
@@ -29,7 +29,6 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexRes
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.util.Assert;
/**
@@ -43,8 +42,7 @@ import org.springframework.util.Assert;
* @author Laurent Canet
* @author Christoph Strobl
*/
public class MongoPersistentEntityIndexCreator implements
ApplicationListener<MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>> {
public class MongoPersistentEntityIndexCreator implements ApplicationListener<MappingContextEvent<?, ?>> {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoPersistentEntityIndexCreator.class);
@@ -54,7 +52,7 @@ public class MongoPersistentEntityIndexCreator implements
private final IndexResolver indexResolver;
/**
* Creats a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and
* Creates a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and
* {@link MongoDbFactory}.
*
* @param mappingContext must not be {@literal null}.
@@ -65,7 +63,7 @@ public class MongoPersistentEntityIndexCreator implements
}
/**
* Creats a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and
* Creates a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and
* {@link MongoDbFactory}.
*
* @param mappingContext must not be {@literal null}.
@@ -92,7 +90,7 @@ public class MongoPersistentEntityIndexCreator implements
* (non-Javadoc)
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
public void onApplicationEvent(MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty> event) {
public void onApplicationEvent(MappingContextEvent<?, ?> event) {
if (!event.wasEmittedBy(mappingContext)) {
return;
@@ -102,7 +100,7 @@ public class MongoPersistentEntityIndexCreator implements
// Double check type as Spring infrastructure does not consider nested generics
if (entity instanceof MongoPersistentEntity) {
checkForIndexes(event.getPersistentEntity());
checkForIndexes((MongoPersistentEntity<?>) entity);
}
}
@@ -125,15 +123,15 @@ public class MongoPersistentEntityIndexCreator implements
private void checkForAndCreateIndexes(MongoPersistentEntity<?> entity) {
if (entity.findAnnotation(Document.class) != null) {
for (IndexDefinitionHolder indexToCreate : indexResolver.resolveIndexForClass(entity.getType())) {
for (IndexDefinitionHolder indexToCreate : indexResolver.resolveIndexFor(entity.getTypeInformation())) {
createIndex(indexToCreate);
}
}
}
private void createIndex(IndexDefinitionHolder indexDefinition) {
mongoDbFactory.getDb().getCollection(indexDefinition.getCollection())
.createIndex(indexDefinition.getIndexKeys(), indexDefinition.getIndexOptions());
mongoDbFactory.getDb().getCollection(indexDefinition.getCollection()).createIndex(indexDefinition.getIndexKeys(),
indexDefinition.getIndexOptions());
}
/**

View File

@@ -36,6 +36,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -51,6 +52,7 @@ import com.mongodb.util.JSON;
* scanning related annotations.
*
* @author Christoph Strobl
* @author Thomas Darimont
* @since 1.5
*/
public class MongoPersistentEntityIndexResolver implements IndexResolver {
@@ -70,13 +72,12 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
this.mappingContext = mappingContext;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexResolver#resolveIndexForClass(java.lang.Class)
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.IndexResolver#resolveIndexForClass(org.springframework.data.util.TypeInformation)
*/
@Override
public List<IndexDefinitionHolder> resolveIndexForClass(Class<?> type) {
return resolveIndexForEntity(mappingContext.getPersistentEntity(type));
public Iterable<? extends IndexDefinitionHolder> resolveIndexFor(TypeInformation<?> typeInformation) {
return resolveIndexForEntity(mappingContext.getPersistentEntity(typeInformation));
}
/**
@@ -259,7 +260,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
LOGGER.info(e.getMessage(), e);
} catch (InvalidDataAccessApiUsageException e) {
LOGGER.info(
String.format("Potentially invald index structure discovered. Breaking operation for %s.",
String.format("Potentially invalid index structure discovered. Breaking operation for %s.",
entity.getName()), e);
}
} else if (includeOptions.isForce() || indexed != null) {

View File

@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core.mapping;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
@@ -305,28 +306,44 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
*/
private static class PropertyTypeAssertionHandler implements PropertyHandler<MongoPersistentProperty> {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PropertyHandler#doWithPersistentProperty(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
potentiallyAssertTextScoreType(persistentProperty);
potentiallyAssertLanguageType(persistentProperty);
potentiallyAssertDBRefTargetType(persistentProperty);
}
private void potentiallyAssertLanguageType(MongoPersistentProperty persistentProperty) {
private static void potentiallyAssertLanguageType(MongoPersistentProperty persistentProperty) {
if (persistentProperty.isExplicitLanguageProperty()) {
assertPropertyType(persistentProperty, String.class);
}
}
private void potentiallyAssertTextScoreType(MongoPersistentProperty persistentProperty) {
private static void potentiallyAssertTextScoreType(MongoPersistentProperty persistentProperty) {
if (persistentProperty.isTextScoreProperty()) {
assertPropertyType(persistentProperty, Float.class, Double.class);
}
}
private void assertPropertyType(MongoPersistentProperty persistentProperty, Class<?>... validMatches) {
private static void potentiallyAssertDBRefTargetType(MongoPersistentProperty persistentProperty) {
if (persistentProperty.isDbReference() && persistentProperty.getDBRef().lazy()) {
if (persistentProperty.isArray() || Modifier.isFinal(persistentProperty.getActualType().getModifiers())) {
throw new MappingException(String.format(
"Invalid lazy DBRef property for %s. Found %s which must not be an array nor a final class.",
persistentProperty.getField(), persistentProperty.getActualType()));
}
}
}
private static void assertPropertyType(MongoPersistentProperty persistentProperty, Class<?>... validMatches) {
for (Class<?> potentialMatch : validMatches) {
if (ClassUtils.isAssignable(potentialMatch, persistentProperty.getActualType())) {
@@ -334,10 +351,9 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
}
}
throw new MappingException(String.format("Missmatching types for %s. Found %s expected one of %s.",
persistentProperty.getField(), persistentProperty.getActualType(),
StringUtils.arrayToCommaDelimitedString(validMatches)));
throw new MappingException(
String.format("Missmatching types for %s. Found %s expected one of %s.", persistentProperty.getField(),
persistentProperty.getActualType(), StringUtils.arrayToCommaDelimitedString(validMatches)));
}
}
}

View File

@@ -45,7 +45,7 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
int getFieldOrder();
/**
* Returns whether the propert is a {@link com.mongodb.DBRef}. If this returns {@literal true} you can expect
* Returns whether the property is a {@link com.mongodb.DBRef}. If this returns {@literal true} you can expect
* {@link #getDBRef()} to return an non-{@literal null} value.
*
* @return

View File

@@ -176,7 +176,7 @@ public class Query {
for (Order order : sort) {
if (order.isIgnoreCase()) {
throw new IllegalArgumentException(String.format("Gven sort contained an Order for %s with ignore case! "
throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! "
+ "MongoDB does not support sorting ignoreing case currently!", order.getProperty()));
}
}

View File

@@ -63,7 +63,7 @@ public class TextCriteria implements CriteriaDefinition {
}
/**
* For a full list of supported languages see the mongdodb reference manual for <a
* For a full list of supported languages see the mongodb reference manual for <a
* href="http://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages</a>.
*
* @param language

View File

@@ -64,7 +64,7 @@ public class Update {
}
/**
* Creates an {@link Update} instance from the given {@link DBObject}. Allows to explicitly exlude fields from making
* Creates an {@link Update} instance from the given {@link DBObject}. Allows to explicitly exclude fields from making
* it into the created {@link Update} object. Note, that this will set attributes directly and <em>not</em> use
* {@literal $set}. This means fields not given in the {@link DBObject} will be nulled when executing the update. To
* create an only-updating {@link Update} instance of a {@link DBObject}, call {@link #set(String, Object)} for each

View File

@@ -38,7 +38,7 @@ import org.springframework.data.annotation.QueryAnnotation;
public @interface Query {
/**
* Takes a MongoDB JSON string to define the actual query to be executed. This one will take precendece over the
* Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the
* method name then.
*
* @return

View File

@@ -20,6 +20,7 @@ import static org.springframework.data.mongodb.core.query.Criteria.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,6 +30,7 @@ import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Shape;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
@@ -44,6 +46,7 @@ import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Custom query creator to create Mongo criterias.
@@ -55,6 +58,7 @@ import org.springframework.util.Assert;
class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
private static final Logger LOG = LoggerFactory.getLogger(MongoQueryCreator.class);
private static final Pattern PUNCTATION_PATTERN = Pattern.compile("\\p{Punct}");
private final MongoParameterAccessor accessor;
private final boolean isGeoNearQuery;
@@ -276,19 +280,23 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
private Criteria createLikeRegexCriteriaOrThrow(Part part, MongoPersistentProperty property, Criteria criteria,
PotentiallyConvertingIterator parameters, boolean shouldNegateExpression) {
PropertyPath path = part.getProperty().getLeafProperty();
switch (part.shouldIgnoreCase()) {
case ALWAYS:
if (part.getProperty().getType() != String.class) {
throw new IllegalArgumentException(String.format("part %s must be of type String but was %s",
part.getProperty(), part.getType()));
if (path.getType() != String.class) {
throw new IllegalArgumentException(
String.format("Part %s must be of type String but was %s", path, path.getType()));
}
// fall-through
case WHEN_POSSIBLE:
if (shouldNegateExpression) {
criteria = criteria.not();
}
return addAppropriateLikeRegexTo(criteria, part, parameters.nextConverted(property).toString());
case NEVER:
@@ -365,8 +373,8 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
return (T) parameter;
}
throw new IllegalArgumentException(String.format("Expected parameter type of %s but got %s!", type,
parameter.getClass()));
throw new IllegalArgumentException(
String.format("Expected parameter type of %s but got %s!", type, parameter.getClass()));
}
private Object[] nextAsArray(PotentiallyConvertingIterator iterator, MongoPersistentProperty property) {
@@ -384,25 +392,59 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
private String toLikeRegex(String source, Part part) {
Type type = part.getType();
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, part);
switch (type) {
case STARTING_WITH:
source = "^" + source;
regex = "^" + regex;
break;
case ENDING_WITH:
source = source + "$";
regex = regex + "$";
break;
case CONTAINING:
case NOT_CONTAINING:
source = "*" + source + "*";
regex = ".*" + regex + ".*";
break;
case SIMPLE_PROPERTY:
case NEGATING_SIMPLE_PROPERTY:
source = "^" + source + "$";
regex = "^" + regex + "$";
default:
}
return source.replaceAll("\\*", ".*");
return regex;
}
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Part qpart) {
if (!ObjectUtils.nullSafeEquals(Type.LIKE, qpart.getType())) {
return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source;
}
if (source.equals("*")) {
return ".*";
}
StringBuilder sb = new StringBuilder();
boolean leadingWildcard = source.startsWith("*");
boolean trailingWildcard = source.endsWith("*");
String valueToUse = source.substring(leadingWildcard ? 1 : 0,
trailingWildcard ? source.length() - 1 : source.length());
if (PUNCTATION_PATTERN.matcher(valueToUse).find()) {
valueToUse = Pattern.quote(valueToUse);
}
if (leadingWildcard) {
sb.append(".*");
}
sb.append(valueToUse);
if (trailingWildcard) {
sb.append(".*");
}
return sb.toString();
}
private boolean isSpherical(MongoPersistentProperty property) {

View File

@@ -35,7 +35,7 @@ import com.mysema.query.apt.Configuration;
import com.mysema.query.apt.DefaultConfiguration;
/**
* Annotation processor to create Querydsl query types for QueryDsl annoated classes.
* Annotation processor to create Querydsl query types for QueryDsl annotated classes.
*
* @author Oliver Gierke
*/

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

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-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.
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
@@ -27,35 +28,37 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.CannotGetMongoDbConnectionException;
import org.springframework.data.mongodb.util.MongoClientVersion;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
import com.mongodb.DB;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
/**
* Unit tests for {@link MongoDbUtils}.
*
* @author Oliver Gierke
* @author Randy Watler
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoDbUtilsUnitTests {
@Mock Mongo mongo;
@Mock MongoClient mongoClientMock;
@Mock DB dbMock;
@Before
public void setUp() throws Exception {
when(mongo.getDB(anyString())).then(new Answer<DB>() {
public DB answer(InvocationOnMock invocation) throws Throwable {
return mock(DB.class);
}
});
when(mongo.getDB(anyString())).thenReturn(dbMock).thenReturn(mock(DB.class));
when(mongoClientMock.getDB(anyString())).thenReturn(dbMock);
TransactionSynchronizationManager.initSynchronization();
}
@@ -151,6 +154,38 @@ public class MongoDbUtilsUnitTests {
assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true));
}
/**
* @see DATAMONGO-1218
*/
@Test
@SuppressWarnings("deprecation")
public void getDBDAuthenticateViaAuthDbWhenCalledWithMongoInstance() {
assumeThat(MongoClientVersion.isMongo3Driver(), is(false));
when(dbMock.getName()).thenReturn("db");
try {
MongoDbUtils.getDB(mongo, "db", new UserCredentials("shallan", "davar"), "authdb");
} catch (CannotGetMongoDbConnectionException e) {
// need to catch that one since we cannot answer the reflective call sufficiently
}
verify(mongo, times(1)).getDB("authdb");
}
/**
* @see DATAMONGO-1218
*/
@Test
@SuppressWarnings("deprecation")
public void getDBDShouldSkipAuthenticationViaAuthDbWhenCalledWithMongoClientInstance() {
MongoDbUtils.getDB(mongoClientMock, "db", new UserCredentials("dalinar", "kholin"), "authdb");
verify(mongoClientMock, never()).getDB("authdb");
}
/**
* Simulate transaction rollback/commit completion protocol on managed transaction synchronizations which will unbind
* managed transaction resources. Does not swallow exceptions for testing purposes.

View File

@@ -73,8 +73,10 @@ import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.util.CloseableIterator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
@@ -187,9 +189,14 @@ public class MongoTemplateTests {
template.dropCollection(DocumentWithCollection.class);
template.dropCollection(DocumentWithCollectionOfSimpleType.class);
template.dropCollection(DocumentWithMultipleCollections.class);
template.dropCollection(DocumentWithNestedCollection.class);
template.dropCollection(DocumentWithEmbeddedDocumentWithCollection.class);
template.dropCollection(DocumentWithNestedList.class);
template.dropCollection(DocumentWithDBRefCollection.class);
template.dropCollection(SomeContent.class);
template.dropCollection(SomeTemplate.class);
template.dropCollection(Address.class);
template.dropCollection(DocumentWithCollectionOfSamples.class);
}
@Test
@@ -2204,6 +2211,243 @@ public class MongoTemplateTests {
assertThat(retrieved.model.value(), equalTo("value2"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenWholeCollectionIsReplaced() {
DocumentWithNestedCollection doc = new DocumentWithNestedCollection();
Map<String, Model> entry = new HashMap<String, Model>();
entry.put("key1", new ModelA("value1"));
doc.models.add(entry);
template.save(doc);
entry.put("key2", new ModelA("value2"));
Query query = query(where("id").is(doc.id));
Update update = Update.update("models", Collections.singletonList(entry));
assertThat(template.findOne(query, DocumentWithNestedCollection.class), notNullValue());
template.findAndModify(query, update, DocumentWithNestedCollection.class);
DocumentWithNestedCollection retrieved = template.findOne(query, DocumentWithNestedCollection.class);
assertThat(retrieved, is(notNullValue()));
assertThat(retrieved.id, is(doc.id));
assertThat(retrieved.models.get(0).entrySet(), hasSize(2));
assertThat(retrieved.models.get(0).get("key1"), instanceOf(ModelA.class));
assertThat(retrieved.models.get(0).get("key1").value(), equalTo("value1"));
assertThat(retrieved.models.get(0).get("key2"), instanceOf(ModelA.class));
assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenFirstElementIsReplaced() {
DocumentWithNestedCollection doc = new DocumentWithNestedCollection();
Map<String, Model> entry = new HashMap<String, Model>();
entry.put("key1", new ModelA("value1"));
doc.models.add(entry);
template.save(doc);
entry.put("key2", new ModelA("value2"));
Query query = query(where("id").is(doc.id));
Update update = Update.update("models.0", entry);
assertThat(template.findOne(query, DocumentWithNestedCollection.class), notNullValue());
template.findAndModify(query, update, DocumentWithNestedCollection.class);
DocumentWithNestedCollection retrieved = template.findOne(query, DocumentWithNestedCollection.class);
assertThat(retrieved, is(notNullValue()));
assertThat(retrieved.id, is(doc.id));
assertThat(retrieved.models.get(0).entrySet(), hasSize(2));
assertThat(retrieved.models.get(0).get("key1"), instanceOf(ModelA.class));
assertThat(retrieved.models.get(0).get("key1").value(), equalTo("value1"));
assertThat(retrieved.models.get(0).get("key2"), instanceOf(ModelA.class));
assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollectionObjectInsertedAtSecondIndex() {
DocumentWithNestedCollection doc = new DocumentWithNestedCollection();
Map<String, Model> entry = new HashMap<String, Model>();
entry.put("key1", new ModelA("value1"));
doc.models.add(entry);
template.save(doc);
Query query = query(where("id").is(doc.id));
Update update = Update.update("models.1", Collections.singletonMap("key2", new ModelA("value2")));
assertThat(template.findOne(query, DocumentWithNestedCollection.class), notNullValue());
template.findAndModify(query, update, DocumentWithNestedCollection.class);
DocumentWithNestedCollection retrieved = template.findOne(query, DocumentWithNestedCollection.class);
assertThat(retrieved, is(notNullValue()));
assertThat(retrieved.id, is(doc.id));
assertThat(retrieved.models.get(0).entrySet(), hasSize(1));
assertThat(retrieved.models.get(1).entrySet(), hasSize(1));
assertThat(retrieved.models.get(0).get("key1"), instanceOf(ModelA.class));
assertThat(retrieved.models.get(0).get("key1").value(), equalTo("value1"));
assertThat(retrieved.models.get(1).get("key2"), instanceOf(ModelA.class));
assertThat(retrieved.models.get(1).get("key2").value(), equalTo("value2"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingPositionedElement()
throws Exception {
List<Model> models = new ArrayList<Model>();
models.add(new ModelA("value1"));
DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(
new DocumentWithCollection(models));
template.save(doc);
Query query = query(where("id").is(doc.id));
Update update = Update.update("embeddedDocument.models.0", new ModelA("value2"));
assertThat(template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class), notNullValue());
template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class);
DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query,
DocumentWithEmbeddedDocumentWithCollection.class);
assertThat(retrieved, notNullValue());
assertThat(retrieved.embeddedDocument.models, hasSize(1));
assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingSecondElement()
throws Exception {
List<Model> models = new ArrayList<Model>();
models.add(new ModelA("value1"));
DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(
new DocumentWithCollection(models));
template.save(doc);
Query query = query(where("id").is(doc.id));
Update update = Update.update("embeddedDocument.models.1", new ModelA("value2"));
assertThat(template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class), notNullValue());
template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class);
DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query,
DocumentWithEmbeddedDocumentWithCollection.class);
assertThat(retrieved, notNullValue());
assertThat(retrieved.embeddedDocument.models, hasSize(2));
assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value1"));
assertThat(retrieved.embeddedDocument.models.get(1).value(), is("value2"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenRewriting()
throws Exception {
List<Model> models = Arrays.<Model> asList(new ModelA("value1"));
DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(
new DocumentWithCollection(models));
template.save(doc);
Query query = query(where("id").is(doc.id));
Update update = Update.update("embeddedDocument",
new DocumentWithCollection(Arrays.<Model> asList(new ModelA("value2"))));
assertThat(template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class), notNullValue());
template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class);
DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query,
DocumentWithEmbeddedDocumentWithCollection.class);
assertThat(retrieved, notNullValue());
assertThat(retrieved.embeddedDocument.models, hasSize(1));
assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWithNestedLists() {
DocumentWithNestedList doc = new DocumentWithNestedList();
List<Model> entry = new ArrayList<Model>();
entry.add(new ModelA("value1"));
doc.models.add(entry);
template.save(doc);
Query query = query(where("id").is(doc.id));
assertThat(template.findOne(query, DocumentWithNestedList.class), notNullValue());
Update update = Update.update("models.0.1", new ModelA("value2"));
template.findAndModify(query, update, DocumentWithNestedList.class);
DocumentWithNestedList retrieved = template.findOne(query, DocumentWithNestedList.class);
assertThat(retrieved, is(notNullValue()));
assertThat(retrieved.id, is(doc.id));
assertThat(retrieved.models.get(0), hasSize(2));
assertThat(retrieved.models.get(0).get(0), instanceOf(ModelA.class));
assertThat(retrieved.models.get(0).get(0).value(), equalTo("value1"));
assertThat(retrieved.models.get(0).get(1), instanceOf(ModelA.class));
assertThat(retrieved.models.get(0).get(1).value(), equalTo("value2"));
}
/**
* @see DATAMONGO-407
*/
@@ -2609,6 +2853,33 @@ public class MongoTemplateTests {
assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void findAndModifyAddToSetWithEachShouldNotAddDuplicatesNorTypeHintForSimpleDocuments() {
DocumentWithCollectionOfSamples doc = new DocumentWithCollectionOfSamples();
doc.samples = Arrays.asList(new Sample(null, "sample1"));
template.save(doc);
Query query = query(where("id").is(doc.id));
assertThat(template.findOne(query, DocumentWithCollectionOfSamples.class), notNullValue());
Update update = new Update().addToSet("samples").each(new Sample(null, "sample2"), new Sample(null, "sample1"));
template.findAndModify(query, update, DocumentWithCollectionOfSamples.class);
DocumentWithCollectionOfSamples retrieved = template.findOne(query, DocumentWithCollectionOfSamples.class);
assertThat(retrieved, notNullValue());
assertThat(retrieved.samples, hasSize(2));
assertThat(retrieved.samples.get(0).field, is("sample1"));
assertThat(retrieved.samples.get(1).field, is("sample2"));
}
/**
* @see DATAMONGO-888
*/
@@ -2723,6 +2994,61 @@ public class MongoTemplateTests {
assertThat(template.findAll(DBObject.class, "collection"), hasSize(0));
}
/**
* @see DATAMONGO-1207
*/
@Test
public void ignoresNullElementsForInsertAll() {
Address newYork = new Address("NY", "New York");
Address washington = new Address("DC", "Washington");
template.insertAll(Arrays.asList(newYork, null, washington));
List<Address> result = template.findAll(Address.class);
assertThat(result, hasSize(2));
assertThat(result, hasItems(newYork, washington));
}
/**
* @see DATAMONGO-1208
*/
@Test
public void takesSortIntoAccountWhenStreaming() {
Person youngestPerson = new Person("John", 20);
Person oldestPerson = new Person("Jane", 42);
template.insertAll(Arrays.asList(oldestPerson, youngestPerson));
Query q = new Query();
q.with(new Sort(Direction.ASC, "age"));
CloseableIterator<Person> stream = template.stream(q, Person.class);
assertThat(stream.next().getAge(), is(youngestPerson.getAge()));
assertThat(stream.next().getAge(), is(oldestPerson.getAge()));
}
/**
* @see DATAMONGO-1208
*/
@Test
public void takesLimitIntoAccountWhenStreaming() {
Person youngestPerson = new Person("John", 20);
Person oldestPerson = new Person("Jane", 42);
template.insertAll(Arrays.asList(oldestPerson, youngestPerson));
Query q = new Query();
q.with(new PageRequest(0, 1, new Sort(Direction.ASC, "age")));
CloseableIterator<Person> stream = template.stream(q, Person.class);
assertThat(stream.next().getAge(), is(youngestPerson.getAge()));
assertThat(stream.hasNext(), is(false));
}
static class DoucmentWithNamedIdField {
@Id String someIdKey;
@@ -2798,12 +3124,36 @@ public class MongoTemplateTests {
List<String> values;
}
static class DocumentWithCollectionOfSamples {
@Id String id;
List<Sample> samples;
}
static class DocumentWithMultipleCollections {
@Id String id;
List<String> string1;
List<String> string2;
}
static class DocumentWithNestedCollection {
@Id String id;
List<Map<String, Model>> models = new ArrayList<Map<String, Model>>();
}
static class DocumentWithNestedList {
@Id String id;
List<List<Model>> models = new ArrayList<List<Model>>();
}
static class DocumentWithEmbeddedDocumentWithCollection {
@Id String id;
DocumentWithCollection embeddedDocument;
DocumentWithEmbeddedDocumentWithCollection(DocumentWithCollection embeddedDocument) {
this.embeddedDocument = embeddedDocument;
}
}
static interface Model {
String value();
@@ -2909,6 +3259,41 @@ public class MongoTemplateTests {
String state;
String city;
Address() {}
Address(String state, String city) {
this.state = state;
this.city = city;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Address)) {
return false;
}
Address that = (Address) obj;
return ObjectUtils.nullSafeEquals(this.city, that.city) && //
ObjectUtils.nullSafeEquals(this.state, that.state);
}
@Override
public int hashCode() {
int result = 17;
result += 31 * ObjectUtils.nullSafeHashCode(this.city);
result += 31 * ObjectUtils.nullSafeHashCode(this.state);
return result;
}
}
static class VersionedPerson {

View File

@@ -43,7 +43,9 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
@@ -52,18 +54,21 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.CommandResult;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.ReadPreference;
/**
* Unit tests for {@link MongoTemplate}.
@@ -353,6 +358,70 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
assertThat(captor.getValue(), equalTo(new BasicDBObjectBuilder().add("foo", 1).get()));
}
/**
* @see DATAMONGO-1166
*/
@Test
public void aggregateShouldHonorReadPreferenceWhenSet() {
when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn(
mock(CommandResult.class));
when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.class));
template.setReadPreference(ReadPreference.secondary());
template.aggregate(Aggregation.newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class);
verify(this.db, times(1)).command(Mockito.any(DBObject.class), eq(ReadPreference.secondary()));
}
/**
* @see DATAMONGO-1166
*/
@Test
public void aggregateShouldIgnoreReadPreferenceWhenNotSet() {
when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn(
mock(CommandResult.class));
when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.class));
template.aggregate(Aggregation.newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class);
verify(this.db, times(1)).command(Mockito.any(DBObject.class));
}
/**
* @see DATAMONGO-1166
*/
@Test
public void geoNearShouldHonorReadPreferenceWhenSet() {
when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn(
mock(CommandResult.class));
when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.class));
template.setReadPreference(ReadPreference.secondary());
NearQuery query = NearQuery.near(new Point(1, 1));
template.geoNear(query, Wrapper.class);
verify(this.db, times(1)).command(Mockito.any(DBObject.class), eq(ReadPreference.secondary()));
}
/**
* @see DATAMONGO-1166
*/
@Test
public void geoNearShouldIgnoreReadPreferenceWhenNotSet() {
when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn(
mock(CommandResult.class));
when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.class));
NearQuery query = NearQuery.near(new Point(1, 1));
template.geoNear(query, Wrapper.class);
verify(this.db, times(1)).command(Mockito.any(DBObject.class));
}
class AutogenerateableId {
@Id BigInteger id;

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

@@ -0,0 +1,97 @@
/*
* Copyright 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.
* 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.convert;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.util.MongoClientVersion.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mongodb.MongoDbFactory;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBRef;
/**
* Unit tests for {@link ReflectiveDBRefResolver}.
*
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class ReflectiveDBRefResolverUnitTests {
@Mock MongoDbFactory dbFactoryMock;
@Mock DBRef dbRefMock;
@Mock DB dbMock;
@Mock DBCollection collectionMock;
@Before
public void setUp() {
when(dbRefMock.getCollectionName()).thenReturn("collection-1");
when(dbRefMock.getId()).thenReturn("id-1");
when(dbFactoryMock.getDb()).thenReturn(dbMock);
when(dbMock.getCollection(eq("collection-1"))).thenReturn(collectionMock);
when(collectionMock.findOne(eq("id-1"))).thenReturn(new BasicDBObject("_id", "id-1"));
}
/**
* @see DATAMONGO-1193
*/
@Test
public void fetchShouldNotLookUpDbWhenUsingDriverVersion2() {
assumeThat(isMongo3Driver(), is(false));
ReflectiveDBRefResolver.fetch(dbFactoryMock, dbRefMock);
verify(dbFactoryMock, never()).getDb();
verify(dbFactoryMock, never()).getDb(anyString());
}
/**
* @see DATAMONGO-1193
*/
@Test
public void fetchShouldUseDbToResolveDbRefWhenUsingDriverVersion3() {
assumeThat(isMongo3Driver(), is(true));
assertThat(ReflectiveDBRefResolver.fetch(dbFactoryMock, dbRefMock), notNullValue());
verify(dbFactoryMock, times(1)).getDb();
}
/**
* @see DATAMONGO-1193
*/
@Test(expected = IllegalArgumentException.class)
public void fetchShouldThrowExceptionWhenDbFactoryIsNullUsingDriverVersion3() {
assumeThat(isMongo3Driver(), is(true));
ReflectiveDBRefResolver.fetch(null, dbRefMock);
}
}

View File

@@ -20,9 +20,13 @@ import static org.hamcrest.collection.IsMapContaining.*;
import static org.junit.Assert.*;
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;
import java.util.Map;
import org.hamcrest.Matcher;
import org.hamcrest.collection.IsIterableContainingInOrder;
@@ -39,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;
@@ -428,8 +435,8 @@ public class UpdateMapperUnitTests {
public void rendersNestedDbRefCorrectly() {
Update update = new Update().pull("nested.dbRefAnnotatedList.id", "2");
DBObject mappedObject = mapper
.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Wrapper.class));
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(Wrapper.class));
DBObject pullClause = getAsDBObject(mappedObject, "$pull");
assertThat(pullClause.containsField("mapped.dbRefAnnotatedList"), is(true));
@@ -524,7 +531,6 @@ public class UpdateMapperUnitTests {
assertThat(((DBObject) updateValue).get("_class").toString(),
equalTo("org.springframework.data.mongodb.core.convert.UpdateMapperUnitTests$ModelImpl"));
}
}
/**
@@ -595,6 +601,274 @@ public class UpdateMapperUnitTests {
assertThat($unset, equalTo(new BasicDBObjectBuilder().add("dbRefAnnotatedList.$", 1).get()));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingEachOperatorShouldNotAddTypeInfoForNonInterfaceNonAbstractTypes() {
Update update = new Update().addToSet("nestedDocs").each(new NestedDocument("nested-1"),
new NestedDocument("nested-2"));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithNestedCollection.class));
assertThat(mappedUpdate, isBsonObject().notContaining("$addToSet.nestedDocs.$each.[0]._class"));
assertThat(mappedUpdate, isBsonObject().notContaining("$addToSet.nestedDocs.$each.[1]._class"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingEachOperatorShouldAddTypeHintForInterfaceTypes() {
Update update = new Update().addToSet("models").each(new ModelImpl(1), new ModelImpl(2));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ListModelWrapper.class));
assertThat(mappedUpdate, isBsonObject().containing("$addToSet.models.$each.[0]._class", ModelImpl.class.getName()));
assertThat(mappedUpdate, isBsonObject().containing("$addToSet.models.$each.[1]._class", ModelImpl.class.getName()));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingEachOperatorShouldAddTypeHintForAbstractTypes() {
Update update = new Update().addToSet("list").each(new ConcreteChildClass("foo", "one"),
new ConcreteChildClass("bar", "two"));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
assertThat(mappedUpdate,
isBsonObject().containing("$addToSet.aliased.$each.[0]._class", ConcreteChildClass.class.getName()));
assertThat(mappedUpdate,
isBsonObject().containing("$addToSet.aliased.$each.[1]._class", ConcreteChildClass.class.getName()));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingShouldOnlyRemoveTypeHintFromTopLevelTypeInCaseOfNestedDocument() {
WrapperAroundInterfaceType wait = new WrapperAroundInterfaceType();
wait.interfaceType = new ModelImpl(1);
Update update = new Update().addToSet("listHoldingConcretyTypeWithInterfaceTypeAttribute").each(wait);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DomainTypeWithListOfConcreteTypesHavingSingleInterfaceTypeAttribute.class));
assertThat(mappedUpdate,
isBsonObject().notContaining("$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0]._class"));
assertThat(mappedUpdate,
isBsonObject().containing(
"$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0].interfaceType._class",
ModelImpl.class.getName()));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingShouldRetainTypeInformationOfNestedListWhenUpdatingConcreteyParentType() {
ListModelWrapper lmw = new ListModelWrapper();
lmw.models = Collections.<Model> singletonList(new ModelImpl(1));
Update update = new Update().set("concreteTypeWithListAttributeOfInterfaceType", lmw);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes.class));
assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteTypeWithListAttributeOfInterfaceType._class"));
assertThat(
mappedUpdate,
isBsonObject().containing("$set.concreteTypeWithListAttributeOfInterfaceType.models.[0]._class",
ModelImpl.class.getName()));
}
/**
* @see DATAMONGO-1236
*/
@Test
public void mappingShouldRetainTypeInformationForObjectValues() {
Update update = new Update().set("value", new NestedDocument("kaladin"));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObject.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.value.name", "kaladin"));
assertThat(mappedUpdate, isBsonObject().containing("$set.value._class", NestedDocument.class.getName()));
}
/**
* @see DATAMONGO-1236
*/
@Test
public void mappingShouldNotRetainTypeInformationForConcreteValues() {
Update update = new Update().set("concreteValue", new NestedDocument("shallan"));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObject.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.concreteValue.name", "shallan"));
assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteValue._class"));
}
/**
* @see DATAMONGO-1236
*/
@Test
public void mappingShouldRetainTypeInformationForObjectValuesWithAlias() {
Update update = new Update().set("value", new NestedDocument("adolin"));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithAliasedObject.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.renamed-value.name", "adolin"));
assertThat(mappedUpdate, isBsonObject().containing("$set.renamed-value._class", NestedDocument.class.getName()));
}
/**
* @see DATAMONGO-1236
*/
@Test
public void mappingShouldRetrainTypeInformationWhenValueTypeOfMapDoesNotMatchItsDeclaration() {
Map<Object, Object> map = Collections.<Object, Object> singletonMap("szeth", new NestedDocument("son-son-vallano"));
Update update = new Update().set("map", map);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObjectMap.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.map.szeth.name", "son-son-vallano"));
assertThat(mappedUpdate, isBsonObject().containing("$set.map.szeth._class", NestedDocument.class.getName()));
}
/**
* @see DATAMONGO-1236
*/
@Test
public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDeclaration() {
Map<Object, NestedDocument> map = Collections.<Object, NestedDocument> singletonMap("jasnah", new NestedDocument(
"kholin"));
Update update = new Update().set("concreteMap", map);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObjectMap.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.concreteMap.jasnah.name", "kholin"));
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;
}
static class DomainTypeWithListOfConcreteTypesHavingSingleInterfaceTypeAttribute {
List<WrapperAroundInterfaceType> listHoldingConcretyTypeWithInterfaceTypeAttribute;
}
static class WrapperAroundInterfaceType {
Model interfaceType;
}
@org.springframework.data.mongodb.core.mapping.Document(collection = "DocumentWithReferenceToInterface")
static interface DocumentWithReferenceToInterface {
@@ -631,7 +905,7 @@ public class UpdateMapperUnitTests {
private @Id String id;
@org.springframework.data.mongodb.core.mapping.DBRef//
@org.springframework.data.mongodb.core.mapping.DBRef //
private InterfaceDocumentDefinitionWithoutId referencedDocument;
public String getId() {
@@ -692,10 +966,10 @@ public class UpdateMapperUnitTests {
String id;
@Field("aliased")//
@Field("aliased") //
List<? extends AbstractChildClass> list;
@Field//
@Field //
List<Model> listOfInterface;
public ParentClass(String id, List<? extends AbstractChildClass> list) {
@@ -728,6 +1002,10 @@ public class UpdateMapperUnitTests {
static class DomainEntity {
List<NestedEntity> collectionOfNestedEntities;
public List<NestedEntity> getCollectionOfNestedEntities() {
return collectionOfNestedEntities;
}
}
static class NestedEntity {
@@ -753,10 +1031,10 @@ public class UpdateMapperUnitTests {
@Id public String id;
@org.springframework.data.mongodb.core.mapping.DBRef//
@org.springframework.data.mongodb.core.mapping.DBRef //
public List<Entity> dbRefAnnotatedList;
@org.springframework.data.mongodb.core.mapping.DBRef//
@org.springframework.data.mongodb.core.mapping.DBRef //
public Entity dbRefProperty;
}
@@ -770,4 +1048,87 @@ public class UpdateMapperUnitTests {
@Field("mapped") DocumentWithDBRefCollection nested;
}
static class DocumentWithNestedCollection {
List<NestedDocument> nestedDocs;
}
static class NestedDocument {
String name;
public NestedDocument(String name) {
super();
this.name = name;
}
}
static class EntityWithObject {
Object value;
NestedDocument concreteValue;
}
static class EntityWithAliasedObject {
@Field("renamed-value") Object value;
}
static class EntityWithObjectMap {
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

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-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.
@@ -18,16 +18,20 @@ package org.springframework.data.mongodb.core.index;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.test.util.CleanMongoDB;
import org.springframework.data.mongodb.test.util.MongoVersionRule;
import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
@@ -38,23 +42,22 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class MongoPersistentEntityIndexCreatorIntegrationTests {
public static @ClassRule MongoVersionRule version = MongoVersionRule.atLeast(new Version(2, 6));
static final String SAMPLE_TYPE_COLLECTION_NAME = "sampleEntity";
static final String RECURSIVE_TYPE_COLLECTION_NAME = "recursiveGenericTypes";
public static @ClassRule RuleChain rules = RuleChain.outerRule(MongoVersionRule.atLeast(new Version(2, 6))).around(
CleanMongoDB.indexes(Arrays.asList(SAMPLE_TYPE_COLLECTION_NAME, RECURSIVE_TYPE_COLLECTION_NAME)));
@Autowired @Qualifier("mongo1") MongoOperations templateOne;
@Autowired @Qualifier("mongo2") MongoOperations templateTwo;
@After
public void cleanUp() {
templateOne.dropCollection(SampleEntity.class);
templateTwo.dropCollection(SampleEntity.class);
}
@Test
public void createsIndexForConfiguredMappingContextOnly() {
@@ -62,7 +65,42 @@ public class MongoPersistentEntityIndexCreatorIntegrationTests {
assertThat(indexInfo, hasSize(greaterThan(0)));
assertThat(indexInfo, Matchers.<IndexInfo> hasItem(hasProperty("name", is("prop"))));
indexInfo = templateTwo.indexOps("sampleEntity").getIndexInfo();
indexInfo = templateTwo.indexOps(SAMPLE_TYPE_COLLECTION_NAME).getIndexInfo();
assertThat(indexInfo, hasSize(0));
}
/**
* @see DATAMONGO-1202
*/
@Test
public void shouldHonorIndexedPropertiesWithRecursiveMappings() {
List<IndexInfo> indexInfo = templateOne.indexOps(RecursiveConcreteType.class).getIndexInfo();
assertThat(indexInfo, hasSize(greaterThan(0)));
assertThat(indexInfo, Matchers.<IndexInfo> hasItem(hasProperty("name", is("firstName"))));
}
@Document(collection = RECURSIVE_TYPE_COLLECTION_NAME)
static abstract class RecursiveGenericType<RGT extends RecursiveGenericType<RGT>> {
@Id Long id;
@org.springframework.data.mongodb.core.mapping.DBRef RGT referrer;
@Indexed String firstName;
public RecursiveGenericType(Long id, String firstName, RGT referrer) {
this.firstName = firstName;
this.id = id;
this.referrer = referrer;
}
}
static class RecursiveConcreteType extends RecursiveGenericType<RecursiveConcreteType> {
public RecursiveConcreteType(Long id, String firstName, RecursiveConcreteType referrer) {
super(id, firstName, referrer);
}
}
}

View File

@@ -143,6 +143,89 @@ public class BasicMongoPersistentEntityUnitTests {
verify(propertyMock, never()).getActualType();
}
/**
* @see DATAMONGO-1157
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test(expected = MappingException.class)
public void verifyShouldThrowErrorForLazyDBRefOnFinalClass() {
BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
ClassTypeInformation.from(AnyDocument.class));
org.springframework.data.mongodb.core.mapping.DBRef dbRefMock = mock(
org.springframework.data.mongodb.core.mapping.DBRef.class);
when(propertyMock.isDbReference()).thenReturn(true);
when(propertyMock.getDBRef()).thenReturn(dbRefMock);
when(dbRefMock.lazy()).thenReturn(true);
when(propertyMock.getActualType()).thenReturn((Class) Class.class);
entity.addPersistentProperty(propertyMock);
entity.verify();
}
/**
* @see DATAMONGO-1157
*/
@Test(expected = MappingException.class)
public void verifyShouldThrowErrorForLazyDBRefArray() {
BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
ClassTypeInformation.from(AnyDocument.class));
org.springframework.data.mongodb.core.mapping.DBRef dbRefMock = mock(
org.springframework.data.mongodb.core.mapping.DBRef.class);
when(propertyMock.isDbReference()).thenReturn(true);
when(propertyMock.getDBRef()).thenReturn(dbRefMock);
when(dbRefMock.lazy()).thenReturn(true);
when(propertyMock.isArray()).thenReturn(true);
entity.addPersistentProperty(propertyMock);
entity.verify();
}
/**
* @see DATAMONGO-1157
*/
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void verifyShouldPassForLazyDBRefOnNonArrayNonFinalClass() {
BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
ClassTypeInformation.from(AnyDocument.class));
org.springframework.data.mongodb.core.mapping.DBRef dbRefMock = mock(
org.springframework.data.mongodb.core.mapping.DBRef.class);
when(propertyMock.isDbReference()).thenReturn(true);
when(propertyMock.getDBRef()).thenReturn(dbRefMock);
when(dbRefMock.lazy()).thenReturn(true);
when(propertyMock.getActualType()).thenReturn((Class) Object.class);
entity.addPersistentProperty(propertyMock);
entity.verify();
verify(propertyMock, times(1)).isDbReference();
}
/**
* @see DATAMONGO-1157
*/
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void verifyShouldPassForNonLazyDBRefOnFinalClass() {
BasicMongoPersistentEntity<AnyDocument> entity = new BasicMongoPersistentEntity<AnyDocument>(
ClassTypeInformation.from(AnyDocument.class));
org.springframework.data.mongodb.core.mapping.DBRef dbRefMock = mock(
org.springframework.data.mongodb.core.mapping.DBRef.class);
when(propertyMock.isDbReference()).thenReturn(true);
when(propertyMock.getDBRef()).thenReturn(dbRefMock);
when(dbRefMock.lazy()).thenReturn(false);
when(propertyMock.getActualType()).thenReturn((Class) Class.class);
entity.addPersistentProperty(propertyMock);
entity.verify();
verify(dbRefMock, times(1)).lazy();
}
@Document(collection = "contacts")
class Contact {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011 by the original author(s).
* Copyright 2011-2015 by the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,17 +16,23 @@
package org.springframework.data.mongodb.core.mapping.event;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
public class PersonBeforeSaveListener implements ApplicationListener<BeforeSaveEvent<PersonPojoStringId>> {
import com.mongodb.DBObject;
public final ArrayList<ApplicationEvent> seenEvents = new ArrayList<ApplicationEvent>();
public class PersonBeforeSaveListener extends AbstractMongoEventListener<PersonPojoStringId> {
public void onApplicationEvent(BeforeSaveEvent<PersonPojoStringId> event) {
this.seenEvents.add(event);
public final List<ApplicationEvent> seenEvents = new ArrayList<ApplicationEvent>();
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener#onBeforeSave(java.lang.Object, com.mongodb.DBObject)
*/
@Override
public void onBeforeSave(PersonPojoStringId source, DBObject dbo) {
seenEvents.add(new BeforeSaveEvent<PersonPojoStringId>(source, dbo));
}
}

View File

@@ -20,7 +20,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* Sample contactt domain class.
* Sample contact domain class.
*
* @author Oliver Gierke
*/

View File

@@ -553,6 +553,114 @@ public class MongoQueryCreatorUnitTests {
assertThat(query, is(query(where("address.geo").near(point).minDistance(10D).maxDistance(20D))));
}
/**
* @see DATAMONGO-1229
*/
@Test
public void appliesIgnoreCaseToLeafProperty() {
PartTree tree = new PartTree("findByAddressStreetIgnoreCase", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "Street");
assertThat(new MongoQueryCreator(tree, accessor, context).createQuery(), is(notNullValue()));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void ignoreCaseShouldEscapeSource() {
PartTree tree = new PartTree("findByUsernameIgnoreCase", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "con.flux+");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex("^\\Qcon.flux+\\E$", "i"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() {
PartTree tree = new PartTree("findByUsernameStartingWithIgnoreCase", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "dawns.light+");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex("^\\Qdawns.light+\\E", "i"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() {
PartTree tree = new PartTree("findByUsernameEndingWithIgnoreCase", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "new.ton+");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex("\\Qnew.ton+\\E$", "i"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "*fire.fight+*");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex(".*\\Qfire.fight+\\E.*"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "*steel.heart+");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex(".*\\Qsteel.heart+\\E"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "cala.mity+*");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex("\\Qcala.mity+\\E.*"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "*");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex(".*"))));
}
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname);

View File

@@ -90,6 +90,10 @@ public class IsBsonObject<T extends BSONObject> extends TypeSafeMatcher<T> {
return false;
}
if (o != null && expectation.not) {
return false;
}
}
return true;
}

View File

@@ -16,6 +16,7 @@ include::preface.adoc[]
:leveloffset: +1
include::new-features.adoc[]
include::{spring-data-commons-docs}/dependencies.adoc[]
include::{spring-data-commons-docs}/repositories.adoc[]
:leveloffset: -1

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,94 @@
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).
* 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-1216 - Authentication mechanism PLAIN changes to SCRAM-SHA-1.
* 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-1208 - MongoTemplate.stream(…) does not consider limit, order, sort etc.
* DATAMONGO-1207 - MongoTemplate#doInsertAll throws NullPointerException when passed Collection contains a null item.
* DATAMONGO-1202 - Indexed annotation problems under generics.
* DATAMONGO-1196 - Upgrade build profiles after MongoDB 3.0 Java driver release.
* DATAMONGO-1193 - Prevent unnecessary database lookups when resolving DBRefs on 2.x driver.
* DATAMONGO-1166 - ReadPreference not used for Aggregations.
* DATAMONGO-1157 - Throw meaningful exception when @DbRef is used with unsupported types.
Changes in version 1.8.0.M1 (2015-06-02)
----------------------------------------
* DATAMONGO-1228 - Release 1.8 M1 (Gosling).
* 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-1218 - Deprecate non-MongoClient related configuration options in XML namespace.
* DATAMONGO-1216 - Authentication mechanism PLAIN changes to SCRAM-SHA-1.
* DATAMONGO-1213 - Include new section on Spring Data and Spring Framework dependencies in reference documentation.
* DATAMONGO-1211 - Adapt API changes in Spring Data Commons to simplify custom repository base class registration.
* DATAMONGO-1210 - Inconsistent property order of _class type hint breaks document equality.
* DATAMONGO-1208 - MongoTemplate.stream(…) does not consider limit, order, sort etc.
* DATAMONGO-1207 - MongoTemplate#doInsertAll throws NullPointerException when passed Collection contains a null item.
* DATAMONGO-1202 - Indexed annotation problems under generics.
* DATAMONGO-1196 - Upgrade build profiles after MongoDB 3.0 Java driver release.
* DATAMONGO-1193 - Prevent unnecessary database lookups when resolving DBRefs on 2.x driver.
* DATAMONGO-1192 - Switch back to Spring 4.1's CollectionFactory.
* DATAMONGO-1134 - Add support for $geoIntersects.
* DATAMONGO-990 - Add support for SpEL expressions in @Query.
Changes in version 1.7.0.RELEASE (2015-03-23)
---------------------------------------------
* DATAMONGO-1189 - Release 1.7 GA.

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 1.7 GA
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").