Compare commits
45 Commits
issue/reac
...
1.5.4.RELE
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7c3ef2aa8 | ||
|
|
3320e49c0b | ||
|
|
286efca52d | ||
|
|
92926befc9 | ||
|
|
703f24ae1c | ||
|
|
febe703954 | ||
|
|
440d16ebc6 | ||
|
|
11c2e90736 | ||
|
|
816a567f29 | ||
|
|
e35486759b | ||
|
|
b80c81f861 | ||
|
|
005d21c0b6 | ||
|
|
3c8b7a54d6 | ||
|
|
b2d59f2539 | ||
|
|
1cc2830e95 | ||
|
|
9a4d6f6fb7 | ||
|
|
e96db8b69b | ||
|
|
13ce75779f | ||
|
|
cc785ecf4f | ||
|
|
41fe1809da | ||
|
|
fd5d39f4d9 | ||
|
|
71d97ff53a | ||
|
|
f2f3faef08 | ||
|
|
b1a488098b | ||
|
|
fd18a8b82c | ||
|
|
761d2748c5 | ||
|
|
e2f3966763 | ||
|
|
2b3e5461c2 | ||
|
|
ca4db459c4 | ||
|
|
e0d0a5dc31 | ||
|
|
18761a39c4 | ||
|
|
ad25751dbb | ||
|
|
879ca6d149 | ||
|
|
8a40cf421c | ||
|
|
fbbb7b6bf7 | ||
|
|
0596403081 | ||
|
|
d9eb18df4d | ||
|
|
132e4a9839 | ||
|
|
4acf8caac1 | ||
|
|
9de3c88e6b | ||
|
|
6115c9562b | ||
|
|
963a222616 | ||
|
|
7a2de49ac1 | ||
|
|
8380806d7a | ||
|
|
e60479e47a |
@@ -11,7 +11,7 @@ For a comprehensive treatment of all the Spring Data MongoDB features, please re
|
|||||||
* the [User Guide](http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/)
|
* the [User Guide](http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/)
|
||||||
* the [JavaDocs](http://docs.spring.io/spring-data/mongodb/docs/current/api/) have extensive comments in them as well.
|
* the [JavaDocs](http://docs.spring.io/spring-data/mongodb/docs/current/api/) have extensive comments in them as well.
|
||||||
* the home page of [Spring Data MongoDB](http://projects.spring.io/spring-data-mongodb) contains links to articles and other resources.
|
* the home page of [Spring Data MongoDB](http://projects.spring.io/spring-data-mongodb) contains links to articles and other resources.
|
||||||
* for more detailed questions, use the [forum](http://forum.spring.io/forum/spring-projects/data/nosql).
|
* for more detailed questions, use [Spring Data Mongodb on Stackoverflow](http://stackoverflow.com/questions/tagged/spring-data-mongodb).
|
||||||
|
|
||||||
If you are new to Spring as well as to Spring Data, look for information about [Spring projects](http://projects.spring.io/).
|
If you are new to Spring as well as to Spring Data, look for information about [Spring projects](http://projects.spring.io/).
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ public class MyService {
|
|||||||
|
|
||||||
Here are some ways for you to get involved in the community:
|
Here are some ways for you to get involved in the community:
|
||||||
|
|
||||||
* Get involved with the Spring community on the Spring Community Forums. Please help out on the [forum](http://forum.spring.io/forum/spring-projects/data/nosql) by responding to questions and joining the debate.
|
* Get involved with the Spring community on Stackoverflow and help out on the [spring-data-mongodb](http://stackoverflow.com/questions/tagged/spring-data-mongodb) tag by responding to questions and joining the debate.
|
||||||
* Create [JIRA](https://jira.springframework.org/browse/DATADOC) tickets for bugs and new features and comment and vote on the ones that you are interested in.
|
* Create [JIRA](https://jira.springframework.org/browse/DATADOC) tickets for bugs and new features and comment and vote on the ones that you are interested in.
|
||||||
* Github is for social coding: if you want to write code, we encourage contributions through pull requests from [forks of this repository](http://help.github.com/forking/). If you want to contribute code this way, please reference a JIRA ticket as well covering the specific issue you are addressing.
|
* Github is for social coding: if you want to write code, we encourage contributions through pull requests from [forks of this repository](http://help.github.com/forking/). If you want to contribute code this way, please reference a JIRA ticket as well covering the specific issue you are addressing.
|
||||||
* Watch for upcoming articles on Spring by [subscribing](http://spring.io/blog) to spring.io.
|
* Watch for upcoming articles on Spring by [subscribing](http://spring.io/blog) to spring.io.
|
||||||
|
|||||||
34
pom.xml
34
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>1.5.0.RELEASE</version>
|
<version>1.5.4.RELEASE</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<name>Spring Data MongoDB</name>
|
<name>Spring Data MongoDB</name>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data.build</groupId>
|
<groupId>org.springframework.data.build</groupId>
|
||||||
<artifactId>spring-data-parent</artifactId>
|
<artifactId>spring-data-parent</artifactId>
|
||||||
<version>1.4.0.RELEASE</version>
|
<version>1.4.4.RELEASE</version>
|
||||||
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
|
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -29,8 +29,9 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.type>multi</project.type>
|
<project.type>multi</project.type>
|
||||||
<dist.id>spring-data-mongodb</dist.id>
|
<dist.id>spring-data-mongodb</dist.id>
|
||||||
<springdata.commons>1.8.0.RELEASE</springdata.commons>
|
<springdata.commons>1.8.4.RELEASE</springdata.commons>
|
||||||
<mongo>2.12.1</mongo>
|
<mongo>2.12.1</mongo>
|
||||||
|
<mongo.osgi>2.12.1</mongo.osgi>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<developers>
|
<developers>
|
||||||
@@ -104,10 +105,35 @@
|
|||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
|
|
||||||
<id>mongo-next</id>
|
<id>mongo-next</id>
|
||||||
<properties>
|
<properties>
|
||||||
<mongo>2.12.0</mongo>
|
<mongo>2.12.3-SNAPSHOT</mongo>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>mongo-snapshots</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
|
||||||
|
<id>mongo-3-next</id>
|
||||||
|
<properties>
|
||||||
|
<mongo>3.0.0-SNAPSHOT</mongo>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>mongo-snapshots</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>1.5.0.RELEASE</version>
|
<version>1.5.4.RELEASE</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb</artifactId>
|
<artifactId>spring-data-mongodb</artifactId>
|
||||||
<version>1.5.0.RELEASE</version>
|
<version>1.5.4.RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Import-Package:
|
|||||||
Export-Template:
|
Export-Template:
|
||||||
org.springframework.data.mongodb.crossstore.*;version="${project.version}"
|
org.springframework.data.mongodb.crossstore.*;version="${project.version}"
|
||||||
Import-Template:
|
Import-Template:
|
||||||
com.mongodb.*;version="${mongo:[=.=.=,+1.0.0)}",
|
com.mongodb.*;version="${mongo.osgi:[=.=.=,+1.0.0)}",
|
||||||
javax.persistence.*;version="${jpa:[=.=.=,+1.0.0)}",
|
javax.persistence.*;version="${jpa:[=.=.=,+1.0.0)}",
|
||||||
org.aspectj.*;version="${aspectj:[1.0.0, 2.0.0)}",
|
org.aspectj.*;version="${aspectj:[1.0.0, 2.0.0)}",
|
||||||
org.bson.*;version="0",
|
org.bson.*;version="0",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>1.5.0.RELEASE</version>
|
<version>1.5.4.RELEASE</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>1.5.0.RELEASE</version>
|
<version>1.5.4.RELEASE</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ Bundle-ManifestVersion: 2
|
|||||||
Import-Package:
|
Import-Package:
|
||||||
sun.reflect;version="0";resolution:=optional
|
sun.reflect;version="0";resolution:=optional
|
||||||
Import-Template:
|
Import-Template:
|
||||||
com.mongodb.*;version="${mongo:[=.=.=,+1.0.0)}",
|
com.mongodb.*;version="${mongo.osgi:[=.=.=,+1.0.0)}",
|
||||||
org.apache.log4j.*;version="${log4j:[=.=.=,+1.0.0)}"
|
org.apache.log4j.*;version="${log4j:[=.=.=,+1.0.0)}"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>1.5.0.RELEASE</version>
|
<version>1.5.4.RELEASE</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -189,9 +189,14 @@
|
|||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
|
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
|
<properties>
|
||||||
|
<property>
|
||||||
|
<name>listener</name>
|
||||||
|
<value>org.springframework.data.mongodb.test.util.CleanMongoDBJunitRunListener</value>
|
||||||
|
</property>
|
||||||
|
</properties>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core;
|
|||||||
import static org.springframework.data.domain.Sort.Direction.*;
|
import static org.springframework.data.domain.Sort.Direction.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
@@ -41,6 +43,7 @@ public class DefaultIndexOperations implements IndexOperations {
|
|||||||
|
|
||||||
private static final Double ONE = Double.valueOf(1);
|
private static final Double ONE = Double.valueOf(1);
|
||||||
private static final Double MINUS_ONE = Double.valueOf(-1);
|
private static final Double MINUS_ONE = Double.valueOf(-1);
|
||||||
|
private static final Collection<String> TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere");
|
||||||
|
|
||||||
private final MongoOperations mongoOperations;
|
private final MongoOperations mongoOperations;
|
||||||
private final String collectionName;
|
private final String collectionName;
|
||||||
@@ -140,7 +143,7 @@ public class DefaultIndexOperations implements IndexOperations {
|
|||||||
|
|
||||||
Object value = keyDbObject.get(key);
|
Object value = keyDbObject.get(key);
|
||||||
|
|
||||||
if ("2d".equals(value)) {
|
if (TWO_D_IDENTIFIERS.contains(value)) {
|
||||||
indexFields.add(IndexField.geo(key));
|
indexFields.add(IndexField.geo(key));
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,15 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
|||||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||||
|
|
||||||
|
import com.mongodb.MongoCursorNotFoundException;
|
||||||
import com.mongodb.MongoException;
|
import com.mongodb.MongoException;
|
||||||
import com.mongodb.MongoException.CursorNotFound;
|
import com.mongodb.MongoException.CursorNotFound;
|
||||||
import com.mongodb.MongoException.DuplicateKey;
|
import com.mongodb.MongoException.DuplicateKey;
|
||||||
import com.mongodb.MongoException.Network;
|
import com.mongodb.MongoException.Network;
|
||||||
import com.mongodb.MongoInternalException;
|
import com.mongodb.MongoInternalException;
|
||||||
|
import com.mongodb.MongoServerSelectionException;
|
||||||
|
import com.mongodb.MongoSocketException;
|
||||||
|
import com.mongodb.MongoTimeoutException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple {@link PersistenceExceptionTranslator} for Mongo. Convert the given runtime exception to an appropriate
|
* Simple {@link PersistenceExceptionTranslator} for Mongo. Convert the given runtime exception to an appropriate
|
||||||
@@ -47,21 +51,23 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
|||||||
|
|
||||||
// Check for well-known MongoException subclasses.
|
// Check for well-known MongoException subclasses.
|
||||||
|
|
||||||
// All other MongoExceptions
|
if (ex instanceof DuplicateKey || ex instanceof DuplicateKeyException) {
|
||||||
if (ex instanceof DuplicateKey) {
|
|
||||||
return new DuplicateKeyException(ex.getMessage(), ex);
|
return new DuplicateKeyException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ex instanceof Network) {
|
if (ex instanceof Network || ex instanceof MongoSocketException) {
|
||||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ex instanceof CursorNotFound) {
|
if (ex instanceof CursorNotFound || ex instanceof MongoCursorNotFoundException) {
|
||||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver 2.12 throws this to indicate connection problems. String comparison to avoid hard dependency
|
if (ex instanceof MongoServerSelectionException) {
|
||||||
if (ex.getClass().getName().equals("com.mongodb.MongoServerSelectionException")) {
|
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex instanceof MongoTimeoutException) {
|
||||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +75,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
|||||||
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
|
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All other MongoExceptions
|
||||||
if (ex instanceof MongoException) {
|
if (ex instanceof MongoException) {
|
||||||
|
|
||||||
int code = ((MongoException) ex).getCode();
|
int code = ((MongoException) ex).getCode();
|
||||||
|
|||||||
@@ -1070,17 +1070,22 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@link Entry} containing the {@link MongoPersistentProperty} defining the {@literal id} as
|
* Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s
|
||||||
* {@link Entry#getKey()} and the {@link Id}s property value as its {@link Entry#getValue()}.
|
* property value as its {@link Entry#getValue()}.
|
||||||
*
|
*
|
||||||
* @param object
|
* @param object
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private Map.Entry<MongoPersistentProperty, Object> extractIdPropertyAndValue(Object object) {
|
private Entry<String, Object> extractIdPropertyAndValue(Object object) {
|
||||||
|
|
||||||
Assert.notNull(object, "Id cannot be extracted from 'null'.");
|
Assert.notNull(object, "Id cannot be extracted from 'null'.");
|
||||||
|
|
||||||
Class<?> objectType = object.getClass();
|
Class<?> objectType = object.getClass();
|
||||||
|
|
||||||
|
if (object instanceof DBObject) {
|
||||||
|
return Collections.singletonMap(ID_FIELD, ((DBObject) object).get(ID_FIELD)).entrySet().iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(objectType);
|
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(objectType);
|
||||||
MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty();
|
MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty();
|
||||||
|
|
||||||
@@ -1090,7 +1095,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
|
|
||||||
Object idValue = BeanWrapper.create(object, mongoConverter.getConversionService())
|
Object idValue = BeanWrapper.create(object, mongoConverter.getConversionService())
|
||||||
.getProperty(idProp, Object.class);
|
.getProperty(idProp, Object.class);
|
||||||
return Collections.singletonMap(idProp, idValue).entrySet().iterator().next();
|
return Collections.singletonMap(idProp.getFieldName(), idValue).entrySet().iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1101,8 +1106,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
*/
|
*/
|
||||||
private Query getIdQueryFor(Object object) {
|
private Query getIdQueryFor(Object object) {
|
||||||
|
|
||||||
Map.Entry<MongoPersistentProperty, Object> id = extractIdPropertyAndValue(object);
|
Entry<String, Object> id = extractIdPropertyAndValue(object);
|
||||||
return new Query(where(id.getKey().getFieldName()).is(id.getValue()));
|
return new Query(where(id.getKey()).is(id.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1116,7 +1121,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
Assert.notEmpty(objects, "Cannot create Query for empty collection.");
|
Assert.notEmpty(objects, "Cannot create Query for empty collection.");
|
||||||
|
|
||||||
Iterator<?> it = objects.iterator();
|
Iterator<?> it = objects.iterator();
|
||||||
Map.Entry<MongoPersistentProperty, Object> firstEntry = extractIdPropertyAndValue(it.next());
|
Entry<String, Object> firstEntry = extractIdPropertyAndValue(it.next());
|
||||||
|
|
||||||
ArrayList<Object> ids = new ArrayList<Object>(objects.size());
|
ArrayList<Object> ids = new ArrayList<Object>(objects.size());
|
||||||
ids.add(firstEntry.getValue());
|
ids.add(firstEntry.getValue());
|
||||||
@@ -1125,7 +1130,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
ids.add(extractIdPropertyAndValue(it.next()).getValue());
|
ids.add(extractIdPropertyAndValue(it.next()).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Query(where(firstEntry.getKey().getFieldName()).in(ids));
|
return new Query(where(firstEntry.getKey()).in(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertUpdateableIdIfNotSet(Object entity) {
|
private void assertUpdateableIdIfNotSet(Object entity) {
|
||||||
@@ -1455,13 +1460,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
"Can not use skip or field specification with map reduce operations");
|
"Can not use skip or field specification with map reduce operations");
|
||||||
}
|
}
|
||||||
if (query.getQueryObject() != null) {
|
if (query.getQueryObject() != null) {
|
||||||
copyMapReduceOptions.put("query", query.getQueryObject());
|
copyMapReduceOptions.put("query", queryMapper.getMappedObject(query.getQueryObject(), null));
|
||||||
}
|
}
|
||||||
if (query.getLimit() > 0) {
|
if (query.getLimit() > 0) {
|
||||||
copyMapReduceOptions.put("limit", query.getLimit());
|
copyMapReduceOptions.put("limit", query.getLimit());
|
||||||
}
|
}
|
||||||
if (query.getSortObject() != null) {
|
if (query.getSortObject() != null) {
|
||||||
copyMapReduceOptions.put("sort", query.getSortObject());
|
copyMapReduceOptions.put("sort", queryMapper.getMappedObject(query.getSortObject(), null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return copyMapReduceOptions;
|
return copyMapReduceOptions;
|
||||||
@@ -2179,7 +2184,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
cursorToUse = cursorToUse.limit(query.getLimit());
|
cursorToUse = cursorToUse.limit(query.getLimit());
|
||||||
}
|
}
|
||||||
if (query.getSortObject() != null) {
|
if (query.getSortObject() != null) {
|
||||||
cursorToUse = cursorToUse.sort(getMappedSortObject(query, type));
|
DBObject sortDbo = type != null ? getMappedSortObject(query, type) : query.getSortObject();
|
||||||
|
cursorToUse = cursorToUse.sort(sortDbo);
|
||||||
}
|
}
|
||||||
if (StringUtils.hasText(query.getHint())) {
|
if (StringUtils.hasText(query.getHint())) {
|
||||||
cursorToUse = cursorToUse.hint(query.getHint());
|
cursorToUse = cursorToUse.hint(query.getHint());
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2013 the original author or authors.
|
* Copyright 2013-2014 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -28,7 +28,7 @@ import com.mongodb.DBObject;
|
|||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
*/
|
*/
|
||||||
class LimitOperation implements AggregationOperation {
|
public class LimitOperation implements AggregationOperation {
|
||||||
|
|
||||||
private final long maxElements;
|
private final long maxElements;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2013 the original author or authors.
|
* Copyright 2013-2014 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
package org.springframework.data.mongodb.core.aggregation;
|
package org.springframework.data.mongodb.core.aggregation;
|
||||||
|
|
||||||
import org.springframework.data.mongodb.core.query.Criteria;
|
import org.springframework.data.mongodb.core.query.Criteria;
|
||||||
|
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import com.mongodb.BasicDBObject;
|
import com.mongodb.BasicDBObject;
|
||||||
@@ -32,17 +33,29 @@ import com.mongodb.DBObject;
|
|||||||
*/
|
*/
|
||||||
public class MatchOperation implements AggregationOperation {
|
public class MatchOperation implements AggregationOperation {
|
||||||
|
|
||||||
private final Criteria criteria;
|
private final CriteriaDefinition criteriaDefinition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link MatchOperation} for the given {@link Criteria}.
|
* Creates a new {@link MatchOperation} for the given {@link Criteria}.
|
||||||
*
|
*
|
||||||
* @param criteria must not be {@literal null}.
|
* @param criteria must not be {@literal null}.
|
||||||
|
* @deprecated Use {@link MatchOperation#MatchOperation(CriteriaDefinition)} instead. This constructor is scheduled
|
||||||
|
* for removal in the next versions.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public MatchOperation(Criteria criteria) {
|
public MatchOperation(Criteria criteria) {
|
||||||
|
this((CriteriaDefinition) criteria);
|
||||||
|
}
|
||||||
|
|
||||||
Assert.notNull(criteria, "Criteria must not be null!");
|
/**
|
||||||
this.criteria = criteria;
|
* Creates a new {@link MatchOperation} for the given {@link CriteriaDefinition}.
|
||||||
|
*
|
||||||
|
* @param criteriaDefinition must not be {@literal null}.
|
||||||
|
*/
|
||||||
|
public MatchOperation(CriteriaDefinition criteriaDefinition) {
|
||||||
|
|
||||||
|
Assert.notNull(criteriaDefinition, "Criteria must not be null!");
|
||||||
|
this.criteriaDefinition = criteriaDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -51,6 +64,6 @@ public class MatchOperation implements AggregationOperation {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DBObject toDBObject(AggregationOperationContext context) {
|
public DBObject toDBObject(AggregationOperationContext context) {
|
||||||
return new BasicDBObject("$match", context.getMappedObject(criteria.getCriteriaObject()));
|
return new BasicDBObject("$match", context.getMappedObject(criteriaDefinition.getCriteriaObject()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import org.springframework.data.util.ClassTypeInformation;
|
|||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import com.mongodb.BasicDBList;
|
import com.mongodb.BasicDBList;
|
||||||
@@ -75,6 +76,8 @@ import com.mongodb.DBRef;
|
|||||||
*/
|
*/
|
||||||
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware {
|
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware {
|
||||||
|
|
||||||
|
private static final String INCOMPATIBLE_TYPES = "Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions. Parent object was: %4$s";
|
||||||
|
|
||||||
protected static final Logger LOGGER = LoggerFactory.getLogger(MappingMongoConverter.class);
|
protected static final Logger LOGGER = LoggerFactory.getLogger(MappingMongoConverter.class);
|
||||||
|
|
||||||
protected final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
protected final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||||
@@ -213,6 +216,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
return (S) readMap(typeToUse, dbo, parent);
|
return (S) readMap(typeToUse, dbo, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dbo instanceof BasicDBList) {
|
||||||
|
throw new MappingException(String.format(INCOMPATIBLE_TYPES, dbo, BasicDBList.class, typeToUse.getType(), parent));
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve persistent entity info
|
// Retrieve persistent entity info
|
||||||
MongoPersistentEntity<S> persistentEntity = (MongoPersistentEntity<S>) mappingContext
|
MongoPersistentEntity<S> persistentEntity = (MongoPersistentEntity<S>) mappingContext
|
||||||
.getPersistentEntity(typeToUse);
|
.getPersistentEntity(typeToUse);
|
||||||
@@ -253,8 +260,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object obj = getValueInternal(prop, dbo, evaluator, result);
|
wrapper.setProperty(prop, getValueInternal(prop, dbo, evaluator, result));
|
||||||
wrapper.setProperty(prop, obj);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -263,18 +269,20 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
public void doWithAssociation(Association<MongoPersistentProperty> association) {
|
public void doWithAssociation(Association<MongoPersistentProperty> association) {
|
||||||
|
|
||||||
MongoPersistentProperty property = association.getInverse();
|
MongoPersistentProperty property = association.getInverse();
|
||||||
|
|
||||||
Object value = dbo.get(property.getName());
|
Object value = dbo.get(property.getName());
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
|
DBRef dbref = value instanceof DBRef ? (DBRef) value : null;
|
||||||
Object obj = dbRefResolver.resolveDbRef(property, dbref, new DbRefResolverCallback() {
|
wrapper.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, new DbRefResolverCallback() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object resolve(MongoPersistentProperty property) {
|
public Object resolve(MongoPersistentProperty property) {
|
||||||
return getValueInternal(property, dbo, evaluator, parent);
|
return getValueInternal(property, dbo, evaluator, parent);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
wrapper.setProperty(property, obj);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -314,14 +322,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean handledByCustomConverter = conversions.getCustomWriteTarget(obj.getClass(), DBObject.class) != null;
|
Class<?> entityType = obj.getClass();
|
||||||
TypeInformation<? extends Object> type = ClassTypeInformation.from(obj.getClass());
|
boolean handledByCustomConverter = conversions.getCustomWriteTarget(entityType, DBObject.class) != null;
|
||||||
|
TypeInformation<? extends Object> type = ClassTypeInformation.from(entityType);
|
||||||
|
|
||||||
if (!handledByCustomConverter && !(dbo instanceof BasicDBList)) {
|
if (!handledByCustomConverter && !(dbo instanceof BasicDBList)) {
|
||||||
typeMapper.writeType(type, dbo);
|
typeMapper.writeType(type, dbo);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeInternal(obj, dbo, type);
|
Object target = obj instanceof LazyLoadingProxy ? ((LazyLoadingProxy) obj).initialize() : obj;
|
||||||
|
|
||||||
|
writeInternal(target, dbo, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -337,7 +348,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> customTarget = conversions.getCustomWriteTarget(obj.getClass(), DBObject.class);
|
Class<?> entityType = obj.getClass();
|
||||||
|
Class<?> customTarget = conversions.getCustomWriteTarget(entityType, DBObject.class);
|
||||||
|
|
||||||
if (customTarget != null) {
|
if (customTarget != null) {
|
||||||
DBObject result = conversionService.convert(obj, DBObject.class);
|
DBObject result = conversionService.convert(obj, DBObject.class);
|
||||||
@@ -345,17 +357,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Map.class.isAssignableFrom(obj.getClass())) {
|
if (Map.class.isAssignableFrom(entityType)) {
|
||||||
writeMapInternal((Map<Object, Object>) obj, dbo, ClassTypeInformation.MAP);
|
writeMapInternal((Map<Object, Object>) obj, dbo, ClassTypeInformation.MAP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Collection.class.isAssignableFrom(obj.getClass())) {
|
if (Collection.class.isAssignableFrom(entityType)) {
|
||||||
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (BasicDBList) dbo);
|
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (BasicDBList) dbo);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(obj.getClass());
|
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType);
|
||||||
writeInternal(obj, dbo, entity);
|
writeInternal(obj, dbo, entity);
|
||||||
addCustomTypeKeyIfNecessary(typeHint, obj, dbo);
|
addCustomTypeKeyIfNecessary(typeHint, obj, dbo);
|
||||||
}
|
}
|
||||||
@@ -682,10 +694,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
|
|
||||||
TypeInformation<?> actualType = type != null ? type.getActualType() : null;
|
TypeInformation<?> actualType = type != null ? type.getActualType() : null;
|
||||||
Class<?> reference = actualType == null ? Object.class : actualType.getType();
|
Class<?> reference = actualType == null ? Object.class : actualType.getType();
|
||||||
|
Class<?> valueType = ClassUtils.getUserClass(value.getClass());
|
||||||
|
|
||||||
boolean notTheSameClass = !value.getClass().equals(reference);
|
boolean notTheSameClass = !valueType.equals(reference);
|
||||||
if (notTheSameClass) {
|
if (notTheSameClass) {
|
||||||
typeMapper.writeType(value.getClass(), dbObject);
|
typeMapper.writeType(valueType, dbObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -791,11 +804,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
idMapper.convertId(id));
|
idMapper.convertId(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator eval,
|
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator,
|
||||||
Object parent) {
|
Object parent) {
|
||||||
|
|
||||||
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(dbo, spELContext, parent);
|
return new MongoDbPropertyValueProvider(dbo, evaluator, parent).getPropertyValue(prop);
|
||||||
return provider.getPropertyValue(prop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1012,17 +1024,27 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
|||||||
return dbObject;
|
return dbObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link PropertyValueProvider} to evaluate a SpEL expression if present on the property or simply accesses the field
|
||||||
|
* of the configured source {@link DBObject}.
|
||||||
|
*
|
||||||
|
* @author Oliver Gierke
|
||||||
|
*/
|
||||||
private class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
|
private class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
|
||||||
|
|
||||||
private final DBObjectAccessor source;
|
private final DBObjectAccessor source;
|
||||||
private final SpELExpressionEvaluator evaluator;
|
private final SpELExpressionEvaluator evaluator;
|
||||||
private final Object parent;
|
private final Object parent;
|
||||||
|
|
||||||
public MongoDbPropertyValueProvider(DBObject source, SpELContext factory, Object parent) {
|
/**
|
||||||
this(source, new DefaultSpELExpressionEvaluator(source, factory), parent);
|
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
|
||||||
}
|
* parent object.
|
||||||
|
*
|
||||||
public MongoDbPropertyValueProvider(DBObject source, DefaultSpELExpressionEvaluator evaluator, Object parent) {
|
* @param source must not be {@literal null}.
|
||||||
|
* @param evaluator must not be {@literal null}.
|
||||||
|
* @param parent can be {@literal null}.
|
||||||
|
*/
|
||||||
|
public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, Object parent) {
|
||||||
|
|
||||||
Assert.notNull(source);
|
Assert.notNull(source);
|
||||||
Assert.notNull(evaluator);
|
Assert.notNull(evaluator);
|
||||||
|
|||||||
@@ -73,7 +73,43 @@ public @interface CompoundIndex {
|
|||||||
boolean dropDups() default false;
|
boolean dropDups() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the index to be created.
|
* The name of the index to be created. <br />
|
||||||
|
* <br />
|
||||||
|
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
|
||||||
|
* provided name will be prefixed with the path leading to the entity. <br />
|
||||||
|
* <br />
|
||||||
|
* The structure below
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* @Document
|
||||||
|
* class Root {
|
||||||
|
* Hybrid hybrid;
|
||||||
|
* Nested nested;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @Document
|
||||||
|
* @CompoundIndex(name = "compound_index", def = "{'h1': 1, 'h2': 1}")
|
||||||
|
* class Hybrid {
|
||||||
|
* String h1, h2;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @CompoundIndex(name = "compound_index", def = "{'n1': 1, 'n2': 1}")
|
||||||
|
* class Nested {
|
||||||
|
* String n1, n2;
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* resolves in the following index structures
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* db.root.ensureIndex( { hybrid.h1: 1, hybrid.h2: 1 } , { name: "hybrid.compound_index" } )
|
||||||
|
* db.root.ensureIndex( { nested.n1: 1, nested.n2: 1 } , { name: "nested.compound_index" } )
|
||||||
|
* db.hybrid.ensureIndex( { h1: 1, h2: 1 } , { name: "compound_index" } )
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@@ -107,8 +143,11 @@ public @interface CompoundIndex {
|
|||||||
/**
|
/**
|
||||||
* Configures the number of seconds after which the collection should expire. Defaults to -1 for no expiry.
|
* Configures the number of seconds after which the collection should expire. Defaults to -1 for no expiry.
|
||||||
*
|
*
|
||||||
|
* @deprecated TTL cannot be defined for {@link CompoundIndex} having more than one field as key. Will be removed in
|
||||||
|
* 1.6.
|
||||||
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
|
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
int expireAfterSeconds() default -1;
|
int expireAfterSeconds() default -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,41 @@ import java.lang.annotation.Target;
|
|||||||
public @interface GeoSpatialIndexed {
|
public @interface GeoSpatialIndexed {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the property in the document that contains the [x, y] or radial coordinates to index.
|
* Index name. <br />
|
||||||
|
* <br />
|
||||||
|
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
|
||||||
|
* provided name will be prefixed with the path leading to the entity. <br />
|
||||||
|
* <br />
|
||||||
|
* The structure below
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* @Document
|
||||||
|
* class Root {
|
||||||
|
* Hybrid hybrid;
|
||||||
|
* Nested nested;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @Document
|
||||||
|
* class Hybrid {
|
||||||
|
* @GeoSpatialIndexed(name="index") Point h1;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* class Nested {
|
||||||
|
* @GeoSpatialIndexed(name="index") Point n1;
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* resolves in the following index structures
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* db.root.ensureIndex( { hybrid.h1: "2d" } , { name: "hybrid.index" } )
|
||||||
|
* db.root.ensureIndex( { nested.n1: "2d" } , { name: "nested.index" } )
|
||||||
|
* db.hybrid.ensureIndex( { h1: "2d" } , { name: "index" } )
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -58,7 +58,41 @@ public @interface Indexed {
|
|||||||
boolean dropDups() default false;
|
boolean dropDups() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Index name.
|
* Index name. <br />
|
||||||
|
* <br />
|
||||||
|
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
|
||||||
|
* provided name will be prefixed with the path leading to the entity. <br />
|
||||||
|
* <br />
|
||||||
|
* The structure below
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* @Document
|
||||||
|
* class Root {
|
||||||
|
* Hybrid hybrid;
|
||||||
|
* Nested nested;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @Document
|
||||||
|
* class Hybrid {
|
||||||
|
* @Indexed(name="index") String h1;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* class Nested {
|
||||||
|
* @Indexed(name="index") String n1;
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* resolves in the following index structures
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* db.root.ensureIndex( { hybrid.h1: 1 } , { name: "hybrid.index" } )
|
||||||
|
* db.root.ensureIndex( { nested.n1: 1 } , { name: "nested.index" } )
|
||||||
|
* db.hybrid.ensureIndex( { h1: 1} , { name: "index" } )
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,12 +21,9 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
|
||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.data.mapping.PropertyHandler;
|
import org.springframework.data.mapping.PropertyHandler;
|
||||||
@@ -94,7 +91,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
Assert.notNull(document, "Given entity is not collection root.");
|
Assert.notNull(document, "Given entity is not collection root.");
|
||||||
|
|
||||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
||||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root.getType()));
|
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root));
|
||||||
|
|
||||||
final CycleGuard guard = new CycleGuard();
|
final CycleGuard guard = new CycleGuard();
|
||||||
|
|
||||||
@@ -103,6 +100,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
@Override
|
@Override
|
||||||
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
|
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
|
||||||
|
|
||||||
|
try {
|
||||||
if (persistentProperty.isEntity()) {
|
if (persistentProperty.isEntity()) {
|
||||||
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(),
|
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(),
|
||||||
persistentProperty.getFieldName(), root.getCollection(), guard));
|
persistentProperty.getFieldName(), root.getCollection(), guard));
|
||||||
@@ -113,6 +111,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
if (indexDefinitionHolder != null) {
|
if (indexDefinitionHolder != null) {
|
||||||
indexInformation.add(indexDefinitionHolder);
|
indexInformation.add(indexDefinitionHolder);
|
||||||
}
|
}
|
||||||
|
} catch (CyclicPropertyReferenceException e) {
|
||||||
|
LOGGER.warn(e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -131,10 +132,11 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
private List<IndexDefinitionHolder> resolveIndexForClass(final Class<?> type, final String path,
|
private List<IndexDefinitionHolder> resolveIndexForClass(final Class<?> type, final String path,
|
||||||
final String collection, final CycleGuard guard) {
|
final String collection, final CycleGuard guard) {
|
||||||
|
|
||||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
|
||||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, type));
|
|
||||||
|
|
||||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
|
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
|
||||||
|
|
||||||
|
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
||||||
|
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, entity));
|
||||||
|
|
||||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -176,14 +178,13 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<IndexDefinitionHolder> potentiallyCreateCompoundIndexDefinitions(String dotPath, String collection,
|
private List<IndexDefinitionHolder> potentiallyCreateCompoundIndexDefinitions(String dotPath, String collection,
|
||||||
Class<?> type) {
|
MongoPersistentEntity<?> entity) {
|
||||||
|
|
||||||
if (AnnotationUtils.findAnnotation(type, CompoundIndexes.class) == null
|
if (entity.findAnnotation(CompoundIndexes.class) == null && entity.findAnnotation(CompoundIndex.class) == null) {
|
||||||
&& AnnotationUtils.findAnnotation(type, CompoundIndex.class) == null) {
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return createCompoundIndexDefinitions(dotPath, collection, type);
|
return createCompoundIndexDefinitions(dotPath, collection, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -195,34 +196,35 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
protected List<IndexDefinitionHolder> createCompoundIndexDefinitions(String dotPath, String fallbackCollection,
|
protected List<IndexDefinitionHolder> createCompoundIndexDefinitions(String dotPath, String fallbackCollection,
|
||||||
Class<?> type) {
|
MongoPersistentEntity<?> entity) {
|
||||||
|
|
||||||
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
||||||
CompoundIndexes indexes = AnnotationUtils.findAnnotation(type, CompoundIndexes.class);
|
CompoundIndexes indexes = entity.findAnnotation(CompoundIndexes.class);
|
||||||
|
|
||||||
if (indexes != null) {
|
if (indexes != null) {
|
||||||
for (CompoundIndex index : indexes.value()) {
|
for (CompoundIndex index : indexes.value()) {
|
||||||
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index));
|
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CompoundIndex index = AnnotationUtils.findAnnotation(type, CompoundIndex.class);
|
CompoundIndex index = entity.findAnnotation(CompoundIndex.class);
|
||||||
|
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index));
|
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index, entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexDefinitions;
|
return indexDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String fallbackCollection,
|
protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String fallbackCollection,
|
||||||
CompoundIndex index) {
|
CompoundIndex index, MongoPersistentEntity<?> entity) {
|
||||||
|
|
||||||
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(resolveCompoundIndexKeyFromStringDefinition(
|
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(resolveCompoundIndexKeyFromStringDefinition(
|
||||||
dotPath, index.def()));
|
dotPath, index.def()));
|
||||||
|
|
||||||
if (!index.useGeneratedName()) {
|
if (!index.useGeneratedName()) {
|
||||||
indexDefinition.named(index.name());
|
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index.unique()) {
|
if (index.unique()) {
|
||||||
@@ -237,8 +239,14 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
indexDefinition.background();
|
indexDefinition.background();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index.expireAfterSeconds() >= 0) {
|
int ttl = index.expireAfterSeconds();
|
||||||
indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
|
|
||||||
|
if (ttl >= 0) {
|
||||||
|
if (indexDefinition.getIndexKeys().keySet().size() > 1) {
|
||||||
|
LOGGER.warn("TTL is not supported for compound index with more than one key. TTL={} will be ignored.", ttl);
|
||||||
|
} else {
|
||||||
|
indexDefinition.expire(ttl, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection;
|
String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection;
|
||||||
@@ -287,7 +295,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
|
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
|
||||||
|
|
||||||
if (!index.useGeneratedName()) {
|
if (!index.useGeneratedName()) {
|
||||||
indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : dotPath);
|
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persitentProperty));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index.unique()) {
|
if (index.unique()) {
|
||||||
@@ -329,7 +337,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
indexDefinition.withMin(index.min()).withMax(index.max());
|
indexDefinition.withMin(index.min()).withMax(index.max());
|
||||||
|
|
||||||
if (!index.useGeneratedName()) {
|
if (!index.useGeneratedName()) {
|
||||||
indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : persistentProperty.getName());
|
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persistentProperty));
|
||||||
}
|
}
|
||||||
|
|
||||||
indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField());
|
indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField());
|
||||||
@@ -337,13 +345,30 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String pathAwareIndexName(String indexName, String dotPath, MongoPersistentProperty property) {
|
||||||
|
|
||||||
|
String nameToUse = StringUtils.hasText(indexName) ? indexName : "";
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(dotPath) || (property != null && dotPath.equals(property.getFieldName()))) {
|
||||||
|
return StringUtils.hasText(nameToUse) ? nameToUse : dotPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasText(dotPath)) {
|
||||||
|
|
||||||
|
nameToUse = StringUtils.hasText(nameToUse) ? (property != null ? dotPath.replace("." + property.getFieldName(),
|
||||||
|
"") : dotPath) + "." + nameToUse : dotPath;
|
||||||
|
}
|
||||||
|
return nameToUse;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
|
* {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
|
||||||
* to detect potential cycles within the references.
|
* to detect potential cycles within the references.
|
||||||
*
|
*
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
*/
|
*/
|
||||||
private static class CycleGuard {
|
static class CycleGuard {
|
||||||
|
|
||||||
private final Map<String, List<Path>> propertyTypeMap;
|
private final Map<String, List<Path>> propertyTypeMap;
|
||||||
|
|
||||||
@@ -355,6 +380,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
* @param property The property to inspect
|
* @param property The property to inspect
|
||||||
* @param path The path under which the property can be reached.
|
* @param path The path under which the property can be reached.
|
||||||
* @throws CyclicPropertyReferenceException in case a potential cycle is detected.
|
* @throws CyclicPropertyReferenceException in case a potential cycle is detected.
|
||||||
|
* @see Path#cycles(MongoPersistentProperty, String)
|
||||||
*/
|
*/
|
||||||
void protect(MongoPersistentProperty property, String path) throws CyclicPropertyReferenceException {
|
void protect(MongoPersistentProperty property, String path) throws CyclicPropertyReferenceException {
|
||||||
|
|
||||||
@@ -365,7 +391,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
|
|
||||||
for (Path existingPath : paths) {
|
for (Path existingPath : paths) {
|
||||||
|
|
||||||
if (existingPath.cycles(property)) {
|
if (existingPath.cycles(property, path)) {
|
||||||
paths.add(new Path(property, path));
|
paths.add(new Path(property, path));
|
||||||
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
|
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
|
||||||
existingPath.getPath());
|
existingPath.getPath());
|
||||||
@@ -373,7 +399,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
paths.add(new Path(property, path));
|
paths.add(new Path(property, path));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
ArrayList<Path> paths = new ArrayList<Path>();
|
ArrayList<Path> paths = new ArrayList<Path>();
|
||||||
@@ -386,7 +411,30 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
return property.getOwner().getType().getSimpleName() + ":" + property.getFieldName();
|
return property.getOwner().getType().getSimpleName() + ":" + property.getFieldName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Path {
|
/**
|
||||||
|
* Path defines the property and its full path from the document root. <br />
|
||||||
|
* A {@link Path} with {@literal spring.data.mongodb} would be created for the property {@code Three.mongodb}.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* @Document
|
||||||
|
* class One {
|
||||||
|
* Two spring;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* class Two {
|
||||||
|
* Three data;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* class Three {
|
||||||
|
* String mongodb;
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author Christoph Strobl
|
||||||
|
*/
|
||||||
|
static class Path {
|
||||||
|
|
||||||
private final MongoPersistentProperty property;
|
private final MongoPersistentProperty property;
|
||||||
private final String path;
|
private final String path;
|
||||||
@@ -401,17 +449,23 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean cycles(MongoPersistentProperty property) {
|
/**
|
||||||
|
* Checks whether the given property is owned by the same entity and if it has been already visited by a subset of
|
||||||
|
* the current path. Given {@literal foo.bar.bar} cycles if {@literal foo.bar} has already been visited and
|
||||||
|
* {@code class Bar} contains a property of type {@code Bar}. The previously mentioned path would not cycle if
|
||||||
|
* {@code class Bar} contained a property of type {@code SomeEntity} named {@literal bar}.
|
||||||
|
*
|
||||||
|
* @param property
|
||||||
|
* @param path
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean cycles(MongoPersistentProperty property, String path) {
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile("\\p{Punct}?" + Pattern.quote(property.getFieldName()) + "(\\p{Punct}|\\w)?");
|
if (!property.getOwner().equals(this.property.getOwner())) {
|
||||||
Matcher matcher = pattern.matcher(path);
|
return false;
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
while (matcher.find()) {
|
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return count >= 1 && property.getOwner().getType().equals(this.property.getOwner().getType());
|
return path.contains(this.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,8 +495,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return String.format("Found cycle for field '%s' in type '%s' for path '%s'", propertyName, type.getSimpleName(),
|
return String.format("Found cycle for field '%s' in type '%s' for path '%s'", propertyName,
|
||||||
dotPath);
|
type != null ? type.getSimpleName() : "unknown", dotPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.mongodb.core.query;
|
package org.springframework.data.mongodb.core.query;
|
||||||
|
|
||||||
|
import static org.springframework.util.ObjectUtils.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -40,6 +41,7 @@ import com.mongodb.DBObject;
|
|||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Becca Gaspard
|
* @author Becca Gaspard
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Thomas Darimont
|
||||||
*/
|
*/
|
||||||
public class Update {
|
public class Update {
|
||||||
|
|
||||||
@@ -279,6 +281,7 @@ public class Update {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DBObject getUpdateObject() {
|
public DBObject getUpdateObject() {
|
||||||
|
|
||||||
DBObject dbo = new BasicDBObject();
|
DBObject dbo = new BasicDBObject();
|
||||||
for (String k : modifierOps.keySet()) {
|
for (String k : modifierOps.keySet()) {
|
||||||
dbo.put(k, modifierOps.get(k));
|
dbo.put(k, modifierOps.get(k));
|
||||||
@@ -335,14 +338,52 @@ public class Update {
|
|||||||
return StringUtils.startsWithIgnoreCase(key, "$");
|
return StringUtils.startsWithIgnoreCase(key, "$");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#hashCode()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getUpdateObject().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#equals(java.lang.Object)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Update that = (Update) obj;
|
||||||
|
return this.getUpdateObject().equals(that.getUpdateObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#toString()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return SerializationUtils.serializeToJsonSafely(getUpdateObject());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifiers holds a distinct collection of {@link Modifier}
|
* Modifiers holds a distinct collection of {@link Modifier}
|
||||||
*
|
*
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Thomas Darimont
|
||||||
*/
|
*/
|
||||||
public static class Modifiers {
|
public static class Modifiers {
|
||||||
|
|
||||||
private HashMap<String, Modifier> modifiers;
|
private Map<String, Modifier> modifiers;
|
||||||
|
|
||||||
public Modifiers() {
|
public Modifiers() {
|
||||||
this.modifiers = new LinkedHashMap<String, Modifier>(1);
|
this.modifiers = new LinkedHashMap<String, Modifier>(1);
|
||||||
@@ -355,6 +396,33 @@ public class Update {
|
|||||||
public void addModifier(Modifier modifier) {
|
public void addModifier(Modifier modifier) {
|
||||||
this.modifiers.put(modifier.getKey(), modifier);
|
this.modifiers.put(modifier.getKey(), modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#hashCode()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return nullSafeHashCode(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#equals(java.lang.Object)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Modifiers that = (Modifiers) obj;
|
||||||
|
|
||||||
|
return this.modifiers.equals(that.modifiers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -379,6 +447,7 @@ public class Update {
|
|||||||
* Implementation of {@link Modifier} representing {@code $each}.
|
* Implementation of {@link Modifier} representing {@code $each}.
|
||||||
*
|
*
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Thomas Darimont
|
||||||
*/
|
*/
|
||||||
private static class Each implements Modifier {
|
private static class Each implements Modifier {
|
||||||
|
|
||||||
@@ -399,6 +468,7 @@ public class Update {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object[] convertedValues = new Object[values.length];
|
Object[] convertedValues = new Object[values.length];
|
||||||
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
for (int i = 0; i < values.length; i++) {
|
||||||
convertedValues[i] = values[i];
|
convertedValues[i] = values[i];
|
||||||
}
|
}
|
||||||
@@ -406,21 +476,57 @@ public class Update {
|
|||||||
return convertedValues;
|
return convertedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey()
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
return "$each";
|
return "$each";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue()
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object getValue() {
|
public Object getValue() {
|
||||||
return this.values;
|
return this.values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#hashCode()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return nullSafeHashCode(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#equals(java.lang.Object)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object that) {
|
||||||
|
|
||||||
|
if (this == that) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (that == null || getClass() != that.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullSafeEquals(values, ((Each) that).values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder for creating {@code $push} modifiers
|
* Builder for creating {@code $push} modifiers
|
||||||
*
|
*
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Thomas Darimont
|
||||||
*/
|
*/
|
||||||
public class PushOperatorBuilder {
|
public class PushOperatorBuilder {
|
||||||
|
|
||||||
@@ -453,6 +559,50 @@ public class Update {
|
|||||||
public Update value(Object value) {
|
public Update value(Object value) {
|
||||||
return Update.this.push(key, value);
|
return Update.this.push(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#hashCode()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
|
||||||
|
int result = 17;
|
||||||
|
|
||||||
|
result += 31 * result + getOuterType().hashCode();
|
||||||
|
result += 31 * result + nullSafeHashCode(key);
|
||||||
|
result += 31 * result + nullSafeHashCode(modifiers);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see java.lang.Object#equals(java.lang.Object)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PushOperatorBuilder that = (PushOperatorBuilder) obj;
|
||||||
|
|
||||||
|
if (!getOuterType().equals(that.getOuterType())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullSafeEquals(this.key, that.key) && nullSafeEquals(this.modifiers, that.modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Update getOuterType() {
|
||||||
|
return Update.this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -488,7 +638,5 @@ public class Update {
|
|||||||
public Update value(Object value) {
|
public Update value(Object value) {
|
||||||
return Update.this.addToSet(this.key, value);
|
return Update.this.addToSet(this.key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
|||||||
|
|
||||||
MongoEntityMetadata<?> metadata = method.getEntityInformation();
|
MongoEntityMetadata<?> metadata = method.getEntityInformation();
|
||||||
return countProjection ? operations.count(query, metadata.getJavaType()) : operations.findOne(query,
|
return countProjection ? operations.count(query, metadata.getJavaType()) : operations.findOne(query,
|
||||||
metadata.getJavaType());
|
metadata.getJavaType(), metadata.getCollectionName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +391,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
|||||||
return operations.findAllAndRemove(query, metadata.getJavaType());
|
return operations.findAllAndRemove(query, metadata.getJavaType());
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteResult writeResult = operations.remove(query, metadata.getCollectionName());
|
WriteResult writeResult = operations.remove(query, metadata.getJavaType(), metadata.getCollectionName());
|
||||||
return writeResult != null ? writeResult.getN() : 0L;
|
return writeResult != null ? writeResult.getN() : 0L;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,14 @@ import org.springframework.data.mapping.context.MappingContext;
|
|||||||
import org.springframework.data.mongodb.core.MongoOperations;
|
import org.springframework.data.mongodb.core.MongoOperations;
|
||||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||||
|
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||||
import org.springframework.data.mongodb.core.query.Query;
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
import org.springframework.data.repository.query.QueryMethod;
|
import org.springframework.data.repository.query.QueryMethod;
|
||||||
import org.springframework.data.repository.query.RepositoryQuery;
|
import org.springframework.data.repository.query.RepositoryQuery;
|
||||||
import org.springframework.data.repository.query.parser.PartTree;
|
import org.springframework.data.repository.query.parser.PartTree;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.mongodb.util.JSONParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link RepositoryQuery} implementation for Mongo.
|
* {@link RepositoryQuery} implementation for Mongo.
|
||||||
@@ -67,7 +71,24 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
|||||||
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
||||||
|
|
||||||
MongoQueryCreator creator = new MongoQueryCreator(tree, accessor, context, isGeoNearQuery);
|
MongoQueryCreator creator = new MongoQueryCreator(tree, accessor, context, isGeoNearQuery);
|
||||||
return creator.createQuery();
|
Query query = creator.createQuery();
|
||||||
|
|
||||||
|
String fieldSpec = this.getQueryMethod().getFieldSpecification();
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(fieldSpec)) {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
BasicQuery result = new BasicQuery(query.getQueryObject().toString(), fieldSpec);
|
||||||
|
result.setSortObject(query.getSortObject());
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (JSONParseException o_O) {
|
||||||
|
throw new IllegalStateException(String.format("Invalid query or field specification in %s!", getQueryMethod(),
|
||||||
|
o_O));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.mongodb.repository.query;
|
package org.springframework.data.mongodb.repository.query;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -23,7 +26,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.data.mongodb.core.MongoOperations;
|
import org.springframework.data.mongodb.core.MongoOperations;
|
||||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||||
import org.springframework.data.mongodb.core.query.Query;
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.mongodb.DBObject;
|
||||||
import com.mongodb.util.JSON;
|
import com.mongodb.util.JSON;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,17 +36,20 @@ import com.mongodb.util.JSON;
|
|||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Thomas Darimont
|
||||||
*/
|
*/
|
||||||
public class StringBasedMongoQuery extends AbstractMongoQuery {
|
public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||||
|
|
||||||
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
|
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
|
||||||
private static final Pattern PLACEHOLDER = Pattern.compile("\\?(\\d+)");
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class);
|
private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class);
|
||||||
|
private static final ParameterBindingParser PARSER = ParameterBindingParser.INSTANCE;
|
||||||
|
|
||||||
private final String query;
|
private final String query;
|
||||||
private final String fieldSpec;
|
private final String fieldSpec;
|
||||||
private final boolean isCountQuery;
|
private final boolean isCountQuery;
|
||||||
private final boolean isDeleteQuery;
|
private final boolean isDeleteQuery;
|
||||||
|
private final List<ParameterBinding> queryParameterBindings;
|
||||||
|
private final List<ParameterBinding> fieldSpecParameterBindings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod} and {@link MongoOperations}.
|
* Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod} and {@link MongoOperations}.
|
||||||
@@ -65,7 +73,11 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
|||||||
super(method, mongoOperations);
|
super(method, mongoOperations);
|
||||||
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
|
this.queryParameterBindings = PARSER.parseParameterBindingsFrom(query);
|
||||||
|
|
||||||
this.fieldSpec = method.getFieldSpecification();
|
this.fieldSpec = method.getFieldSpecification();
|
||||||
|
this.fieldSpecParameterBindings = PARSER.parseParameterBindingsFrom(method.getFieldSpecification());
|
||||||
|
|
||||||
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
|
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
|
||||||
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
|
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
|
||||||
|
|
||||||
@@ -81,12 +93,12 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
|||||||
@Override
|
@Override
|
||||||
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
||||||
|
|
||||||
String queryString = replacePlaceholders(query, accessor);
|
String queryString = replacePlaceholders(query, accessor, queryParameterBindings);
|
||||||
|
|
||||||
Query query = null;
|
Query query = null;
|
||||||
|
|
||||||
if (fieldSpec != null) {
|
if (fieldSpec != null) {
|
||||||
String fieldString = replacePlaceholders(fieldSpec, accessor);
|
String fieldString = replacePlaceholders(fieldSpec, accessor, fieldSpecParameterBindings);
|
||||||
query = new BasicQuery(queryString, fieldString);
|
query = new BasicQuery(queryString, fieldString);
|
||||||
} else {
|
} else {
|
||||||
query = new BasicQuery(queryString);
|
query = new BasicQuery(queryString);
|
||||||
@@ -119,21 +131,173 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
|||||||
return this.isDeleteQuery;
|
return this.isDeleteQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String replacePlaceholders(String input, ConvertingParameterAccessor accessor) {
|
/**
|
||||||
|
* Replaced the parameter place-holders with the actual parameter values from the given {@link ParameterBinding}s.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* @param accessor
|
||||||
|
* @param bindings
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String replacePlaceholders(String input, ConvertingParameterAccessor accessor, List<ParameterBinding> bindings) {
|
||||||
|
|
||||||
Matcher matcher = PLACEHOLDER.matcher(input);
|
if (bindings.isEmpty()) {
|
||||||
String result = input;
|
return input;
|
||||||
|
|
||||||
while (matcher.find()) {
|
|
||||||
String group = matcher.group();
|
|
||||||
int index = Integer.parseInt(matcher.group(1));
|
|
||||||
result = result.replace(group, getParameterWithIndex(accessor, index));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
StringBuilder result = new StringBuilder(input);
|
||||||
|
|
||||||
|
for (ParameterBinding binding : bindings) {
|
||||||
|
|
||||||
|
String parameter = binding.getParameter();
|
||||||
|
int idx = result.indexOf(parameter);
|
||||||
|
|
||||||
|
if (idx != -1) {
|
||||||
|
result.replace(idx, idx + parameter.length(), getParameterValueForBinding(accessor, binding));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getParameterWithIndex(ConvertingParameterAccessor accessor, int index) {
|
return result.toString();
|
||||||
return JSON.serialize(accessor.getBindableValue(index));
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the serialized value to be used for the given {@link ParameterBinding}.
|
||||||
|
*
|
||||||
|
* @param accessor
|
||||||
|
* @param binding
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String getParameterValueForBinding(ConvertingParameterAccessor accessor, ParameterBinding binding) {
|
||||||
|
|
||||||
|
Object value = accessor.getBindableValue(binding.getParameterIndex());
|
||||||
|
|
||||||
|
if (value instanceof String && binding.isQuoted()) {
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.serialize(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parser that extracts the parameter bindings from a given query string.
|
||||||
|
*
|
||||||
|
* @author Thomas Darimont
|
||||||
|
*/
|
||||||
|
private static enum ParameterBindingParser {
|
||||||
|
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
private static final String PARAMETER_PREFIX = "_param_";
|
||||||
|
private static final String PARSEABLE_PARAMETER = "\"" + PARAMETER_PREFIX + "$1\"";
|
||||||
|
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
|
||||||
|
private static final Pattern PARSEABLE_BINDING_PATTERN = Pattern.compile("\"?" + PARAMETER_PREFIX + "(\\d+)\"?");
|
||||||
|
|
||||||
|
private final static int PARAMETER_INDEX_GROUP = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of {@link ParameterBinding}s found in the given {@code input} or an
|
||||||
|
* {@link Collections#emptyList()}.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<ParameterBinding> parseParameterBindingsFrom(String input) {
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(input)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
|
||||||
|
|
||||||
|
String parseableInput = makeParameterReferencesParseable(input);
|
||||||
|
|
||||||
|
collectParameterReferencesIntoBindings(bindings, JSON.parse(parseableInput));
|
||||||
|
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeParameterReferencesParseable(String input) {
|
||||||
|
|
||||||
|
Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(input);
|
||||||
|
String parseableInput = matcher.replaceAll(PARSEABLE_PARAMETER);
|
||||||
|
|
||||||
|
return parseableInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectParameterReferencesIntoBindings(List<ParameterBinding> bindings, Object value) {
|
||||||
|
|
||||||
|
if (value instanceof String) {
|
||||||
|
|
||||||
|
String string = ((String) value).trim();
|
||||||
|
|
||||||
|
Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string);
|
||||||
|
while (valueMatcher.find()) {
|
||||||
|
int paramIndex = Integer.parseInt(valueMatcher.group(PARAMETER_INDEX_GROUP));
|
||||||
|
boolean quoted = (string.startsWith("'") && string.endsWith("'"))
|
||||||
|
|| (string.startsWith("\"") && string.endsWith("\""));
|
||||||
|
bindings.add(new ParameterBinding(paramIndex, quoted));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (value instanceof Pattern) {
|
||||||
|
|
||||||
|
String string = ((Pattern) value).toString().trim();
|
||||||
|
|
||||||
|
Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string);
|
||||||
|
while (valueMatcher.find()) {
|
||||||
|
int paramIndex = Integer.parseInt(valueMatcher.group(PARAMETER_INDEX_GROUP));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The pattern is used as a direct parameter replacement, e.g. 'field': ?1,
|
||||||
|
* therefore we treat it as not quoted to remain backwards compatible.
|
||||||
|
*/
|
||||||
|
boolean quoted = !string.equals(PARAMETER_PREFIX + paramIndex);
|
||||||
|
|
||||||
|
bindings.add(new ParameterBinding(paramIndex, quoted));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (value instanceof DBObject) {
|
||||||
|
|
||||||
|
DBObject dbo = (DBObject) value;
|
||||||
|
|
||||||
|
for (String field : dbo.keySet()) {
|
||||||
|
collectParameterReferencesIntoBindings(bindings, dbo.get(field));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic parameter binding with name or position information.
|
||||||
|
*
|
||||||
|
* @author Thomas Darimont
|
||||||
|
*/
|
||||||
|
private static class ParameterBinding {
|
||||||
|
|
||||||
|
private final int parameterIndex;
|
||||||
|
private final boolean quoted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ParameterBinding} with the given {@code parameterIndex} and {@code quoted} information.
|
||||||
|
*
|
||||||
|
* @param parameterIndex
|
||||||
|
* @param quoted whether or not the parameter is already quoted.
|
||||||
|
*/
|
||||||
|
public ParameterBinding(int parameterIndex, boolean quoted) {
|
||||||
|
|
||||||
|
this.parameterIndex = parameterIndex;
|
||||||
|
this.quoted = quoted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQuoted() {
|
||||||
|
return quoted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParameterIndex() {
|
||||||
|
return parameterIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParameter() {
|
||||||
|
return "?" + parameterIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011-2012 the original author or authors.
|
* Copyright 2011-2014 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -25,7 +25,10 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import com.mongodb.DBObject;
|
import com.mongodb.DBObject;
|
||||||
|
import com.mongodb.DBRef;
|
||||||
import com.mysema.query.mongodb.MongodbSerializer;
|
import com.mysema.query.mongodb.MongodbSerializer;
|
||||||
|
import com.mysema.query.types.Constant;
|
||||||
|
import com.mysema.query.types.Operation;
|
||||||
import com.mysema.query.types.Path;
|
import com.mysema.query.types.Path;
|
||||||
import com.mysema.query.types.PathMetadata;
|
import com.mysema.query.types.PathMetadata;
|
||||||
import com.mysema.query.types.PathType;
|
import com.mysema.query.types.PathType;
|
||||||
@@ -34,9 +37,12 @@ import com.mysema.query.types.PathType;
|
|||||||
* Custom {@link MongodbSerializer} to take mapping information into account when building keys for constraints.
|
* Custom {@link MongodbSerializer} to take mapping information into account when building keys for constraints.
|
||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Christoph Strobl
|
||||||
*/
|
*/
|
||||||
class SpringDataMongodbSerializer extends MongodbSerializer {
|
class SpringDataMongodbSerializer extends MongodbSerializer {
|
||||||
|
|
||||||
|
private final String ID_KEY = "_id";
|
||||||
|
|
||||||
private final MongoConverter converter;
|
private final MongoConverter converter;
|
||||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||||
private final QueryMapper mapper;
|
private final QueryMapper mapper;
|
||||||
@@ -44,7 +50,7 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
|
|||||||
/**
|
/**
|
||||||
* Creates a new {@link SpringDataMongodbSerializer} for the given {@link MappingContext}.
|
* Creates a new {@link SpringDataMongodbSerializer} for the given {@link MappingContext}.
|
||||||
*
|
*
|
||||||
* @param mappingContext
|
* @param mappingContext must not be {@literal null}.
|
||||||
*/
|
*/
|
||||||
public SpringDataMongodbSerializer(MongoConverter converter) {
|
public SpringDataMongodbSerializer(MongoConverter converter) {
|
||||||
|
|
||||||
@@ -80,10 +86,63 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
|
|||||||
@Override
|
@Override
|
||||||
protected DBObject asDBObject(String key, Object value) {
|
protected DBObject asDBObject(String key, Object value) {
|
||||||
|
|
||||||
if ("_id".equals(key)) {
|
if (ID_KEY.equals(key)) {
|
||||||
return super.asDBObject(key, mapper.convertId(value));
|
return mapper.getMappedObject(super.asDBObject(key, value), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.asDBObject(key, value instanceof Pattern ? value : converter.convertToMongoType(value));
|
return super.asDBObject(key, value instanceof Pattern ? value : converter.convertToMongoType(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see com.mysema.query.mongodb.MongodbSerializer#isReference(com.mysema.query.types.Path)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean isReference(Path<?> path) {
|
||||||
|
|
||||||
|
MongoPersistentProperty property = getPropertyFor(path);
|
||||||
|
return property == null ? false : property.isAssociation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see com.mysema.query.mongodb.MongodbSerializer#asReference(java.lang.Object)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected DBRef asReference(Object constant) {
|
||||||
|
return converter.toDBRef(constant, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see com.mysema.query.mongodb.MongodbSerializer#asReference(com.mysema.query.types.Operation, int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected DBRef asReference(Operation<?> expr, int constIndex) {
|
||||||
|
|
||||||
|
for (Object arg : expr.getArgs()) {
|
||||||
|
|
||||||
|
if (arg instanceof Path) {
|
||||||
|
|
||||||
|
MongoPersistentProperty property = getPropertyFor((Path<?>) arg);
|
||||||
|
Object constant = ((Constant<?>) expr.getArg(constIndex)).getConstant();
|
||||||
|
|
||||||
|
return converter.toDBRef(constant, property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.asReference(expr, constIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MongoPersistentProperty getPropertyFor(Path<?> path) {
|
||||||
|
|
||||||
|
Path<?> parent = path.getMetadata().getParent();
|
||||||
|
|
||||||
|
if (parent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(parent.getType());
|
||||||
|
return entity != null ? entity.getPersistentProperty(path.getMetadata().getName()) : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 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;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.mongodb.core.index.IndexInfo;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBObject;
|
||||||
|
import com.mongodb.DBCollection;
|
||||||
|
import com.mongodb.DBObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link DefaultIndexOperations}.
|
||||||
|
*
|
||||||
|
* @author Christoph Strobl
|
||||||
|
* @author Oliver Gierke
|
||||||
|
*/
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration("classpath:infrastructure.xml")
|
||||||
|
public class DefaultIndexOperationsIntegrationTests {
|
||||||
|
|
||||||
|
static final DBObject GEO_SPHERE_2D = new BasicDBObject("loaction", "2dsphere");
|
||||||
|
|
||||||
|
@Autowired MongoTemplate template;
|
||||||
|
DefaultIndexOperations indexOps;
|
||||||
|
DBCollection collection;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
|
||||||
|
String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class);
|
||||||
|
|
||||||
|
this.collection = this.template.getDb().getCollection(collectionName);
|
||||||
|
this.collection.dropIndexes();
|
||||||
|
|
||||||
|
this.indexOps = new DefaultIndexOperations(template, collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1008
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getIndexInfoShouldBeAbleToRead2dsphereIndex() {
|
||||||
|
|
||||||
|
collection.createIndex(GEO_SPHERE_2D);
|
||||||
|
|
||||||
|
IndexInfo info = findAndReturnIndexInfo(GEO_SPHERE_2D);
|
||||||
|
assertThat(info.getIndexFields().get(0).isGeo(), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IndexInfo findAndReturnIndexInfo(DBObject keys) {
|
||||||
|
return findAndReturnIndexInfo(indexOps.getIndexInfo(), keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static IndexInfo findAndReturnIndexInfo(Iterable<IndexInfo> candidates, DBObject keys) {
|
||||||
|
return findAndReturnIndexInfo(candidates, DBCollection.genIndexName(keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IndexInfo findAndReturnIndexInfo(Iterable<IndexInfo> candidates, String name) {
|
||||||
|
|
||||||
|
for (IndexInfo info : candidates) {
|
||||||
|
if (ObjectUtils.nullSafeEquals(name, info.getName())) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError(String.format("Index with %s was not found", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DefaultIndexOperationsIntegrationTestsSample {}
|
||||||
|
}
|
||||||
@@ -2511,6 +2511,37 @@ public class MongoTemplateTests {
|
|||||||
assertThat(template.getDb().getCollection("sample").find(new BasicDBObject("field", "data")).count(), is(1));
|
assertThat(template.getDb().getCollection("sample").find(new BasicDBObject("field", "data")).count(), is(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1001
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldAllowSavingOfLazyLoadedDbRefs() {
|
||||||
|
|
||||||
|
template.dropCollection(SomeTemplate.class);
|
||||||
|
template.dropCollection(SomeMessage.class);
|
||||||
|
template.dropCollection(SomeContent.class);
|
||||||
|
|
||||||
|
SomeContent content = new SomeContent();
|
||||||
|
content.id = "content-1";
|
||||||
|
content.text = "spring";
|
||||||
|
template.save(content);
|
||||||
|
|
||||||
|
SomeTemplate tmpl = new SomeTemplate();
|
||||||
|
tmpl.id = "template-1";
|
||||||
|
tmpl.content = content; // @DBRef(lazy=true) tmpl.content
|
||||||
|
|
||||||
|
template.save(tmpl);
|
||||||
|
|
||||||
|
SomeTemplate savedTmpl = template.findById(tmpl.id, SomeTemplate.class);
|
||||||
|
|
||||||
|
SomeContent loadedContent = savedTmpl.getContent();
|
||||||
|
loadedContent.setText("data");
|
||||||
|
template.save(loadedContent);
|
||||||
|
|
||||||
|
assertThat(template.findById(content.id, SomeContent.class).getText(), is("data"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see DATAMONGO-880
|
* @see DATAMONGO-880
|
||||||
*/
|
*/
|
||||||
@@ -2693,6 +2724,22 @@ public class MongoTemplateTests {
|
|||||||
assertThat(result.getContent().getName(), is(content.getName()));
|
assertThat(result.getContent().getName(), is(content.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-970
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void insertsAndRemovesBasicDbObjectCorrectly() {
|
||||||
|
|
||||||
|
BasicDBObject object = new BasicDBObject("key", "value");
|
||||||
|
template.insert(object, "collection");
|
||||||
|
|
||||||
|
assertThat(object.get("_id"), is(notNullValue()));
|
||||||
|
assertThat(template.findAll(DBObject.class, "collection"), hasSize(1));
|
||||||
|
|
||||||
|
template.remove(object, "collection");
|
||||||
|
assertThat(template.findAll(DBObject.class, "collection"), hasSize(0));
|
||||||
|
}
|
||||||
|
|
||||||
static class DoucmentWithNamedIdField {
|
static class DoucmentWithNamedIdField {
|
||||||
|
|
||||||
@Id String someIdKey;
|
@Id String someIdKey;
|
||||||
@@ -2940,6 +2987,11 @@ public class MongoTemplateTests {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import org.springframework.dao.DataAccessException;
|
|||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.annotation.Version;
|
import org.springframework.data.annotation.Version;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.data.mongodb.MongoDbFactory;
|
import org.springframework.data.mongodb.MongoDbFactory;
|
||||||
import org.springframework.data.mongodb.core.convert.CustomConversions;
|
import org.springframework.data.mongodb.core.convert.CustomConversions;
|
||||||
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||||
@@ -50,11 +51,13 @@ import org.springframework.data.mongodb.core.convert.QueryMapper;
|
|||||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
|
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
|
||||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
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.Query;
|
||||||
import org.springframework.data.mongodb.core.query.Update;
|
import org.springframework.data.mongodb.core.query.Update;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import com.mongodb.BasicDBObject;
|
import com.mongodb.BasicDBObject;
|
||||||
|
import com.mongodb.BasicDBObjectBuilder;
|
||||||
import com.mongodb.DB;
|
import com.mongodb.DB;
|
||||||
import com.mongodb.DBCollection;
|
import com.mongodb.DBCollection;
|
||||||
import com.mongodb.DBCursor;
|
import com.mongodb.DBCursor;
|
||||||
@@ -329,6 +332,26 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
|||||||
verify(collection, never()).remove(Mockito.any(DBObject.class));
|
verify(collection, never()).remove(Mockito.any(DBObject.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-948
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void sortShouldBeTakenAsIsWhenExecutingQueryWithoutSpecificTypeInformation() {
|
||||||
|
|
||||||
|
Query query = Query.query(Criteria.where("foo").is("bar")).with(new Sort("foo"));
|
||||||
|
template.executeQuery(query, "collection1", new DocumentCallbackHandler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processDocument(DBObject dbObject) throws MongoException, DataAccessException {
|
||||||
|
// nothing to do - just a test
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ArgumentCaptor<DBObject> captor = ArgumentCaptor.forClass(DBObject.class);
|
||||||
|
verify(cursor, times(1)).sort(captor.capture());
|
||||||
|
assertThat(captor.getValue(), equalTo(new BasicDBObjectBuilder().add("foo", 1).get()));
|
||||||
|
}
|
||||||
|
|
||||||
class AutogenerateableId {
|
class AutogenerateableId {
|
||||||
|
|
||||||
@Id BigInteger id;
|
@Id BigInteger id;
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2014 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;
|
package org.springframework.data.mongodb.core.convert;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
@@ -15,6 +30,7 @@ import org.bson.types.Binary;
|
|||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.aop.framework.ProxyFactory;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.core.convert.support.DefaultConversionService;
|
import org.springframework.core.convert.support.DefaultConversionService;
|
||||||
import org.springframework.core.convert.support.GenericConversionService;
|
import org.springframework.core.convert.support.GenericConversionService;
|
||||||
@@ -26,6 +42,7 @@ import com.mongodb.DBRef;
|
|||||||
* Unit tests for {@link CustomConversions}.
|
* Unit tests for {@link CustomConversions}.
|
||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @auhtor Christoph Strobl
|
||||||
*/
|
*/
|
||||||
public class CustomConversionsUnitTests {
|
public class CustomConversionsUnitTests {
|
||||||
|
|
||||||
@@ -197,6 +214,35 @@ public class CustomConversionsUnitTests {
|
|||||||
assertThat(conversionService.convert(new DateTime(), Date.class), is(new Date(0)));
|
assertThat(conversionService.convert(new DateTime(), Date.class), is(new Date(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1001
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldSelectPropertCustomWriteTargetForCglibProxiedType() {
|
||||||
|
|
||||||
|
CustomConversions conversions = new CustomConversions(Arrays.asList(FormatToStringConverter.INSTANCE));
|
||||||
|
assertThat(conversions.getCustomWriteTarget(createProxyTypeFor(Format.class)), is(typeCompatibleWith(String.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1001
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldSelectPropertCustomReadTargetForCglibProxiedType() {
|
||||||
|
|
||||||
|
CustomConversions conversions = new CustomConversions(Arrays.asList(CustomObjectToStringConverter.INSTANCE));
|
||||||
|
assertThat(conversions.hasCustomReadTarget(createProxyTypeFor(Object.class), String.class), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> createProxyTypeFor(Class<?> type) {
|
||||||
|
|
||||||
|
ProxyFactory factory = new ProxyFactory();
|
||||||
|
factory.setProxyTargetClass(true);
|
||||||
|
factory.setTargetClass(type);
|
||||||
|
|
||||||
|
return factory.getProxy().getClass();
|
||||||
|
}
|
||||||
|
|
||||||
enum FormatToStringConverter implements Converter<Format, String> {
|
enum FormatToStringConverter implements Converter<Format, String> {
|
||||||
INSTANCE;
|
INSTANCE;
|
||||||
|
|
||||||
@@ -251,4 +297,15 @@ public class CustomConversionsUnitTests {
|
|||||||
return new Date(0);
|
return new Date(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CustomObjectToStringConverter implements Converter<Object, String> {
|
||||||
|
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convert(Object source) {
|
||||||
|
return source != null ? source.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -429,6 +429,27 @@ public class DbRefMappingMongoConverterUnitTests {
|
|||||||
assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride2, true);
|
assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride2, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-987
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldNotGenerateLazyLoadingProxyForNullValues() {
|
||||||
|
|
||||||
|
DBObject dbo = new BasicDBObject();
|
||||||
|
ClassWithLazyDbRefs lazyDbRefs = new ClassWithLazyDbRefs();
|
||||||
|
lazyDbRefs.id = "42";
|
||||||
|
converter.write(lazyDbRefs, dbo);
|
||||||
|
|
||||||
|
ClassWithLazyDbRefs result = converter.read(ClassWithLazyDbRefs.class, dbo);
|
||||||
|
|
||||||
|
assertThat(result.id, is(lazyDbRefs.id));
|
||||||
|
assertThat(result.dbRefToInterface, is(nullValue()));
|
||||||
|
assertThat(result.dbRefToConcreteCollection, is(nullValue()));
|
||||||
|
assertThat(result.dbRefToConcreteType, is(nullValue()));
|
||||||
|
assertThat(result.dbRefToConcreteTypeWithPersistenceConstructor, is(nullValue()));
|
||||||
|
assertThat(result.dbRefToConcreteTypeWithPersistenceConstructorWithoutDefaultConstructor, is(nullValue()));
|
||||||
|
}
|
||||||
|
|
||||||
private Object transport(Object result) {
|
private Object transport(Object result) {
|
||||||
return SerializationUtils.deserialize(SerializationUtils.serialize(result));
|
return SerializationUtils.deserialize(SerializationUtils.serialize(result));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,10 +45,13 @@ import org.hamcrest.Matchers;
|
|||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import org.springframework.aop.framework.ProxyFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
@@ -98,6 +101,8 @@ public class MappingMongoConverterUnitTests {
|
|||||||
@Mock ApplicationContext context;
|
@Mock ApplicationContext context;
|
||||||
@Mock DbRefResolver resolver;
|
@Mock DbRefResolver resolver;
|
||||||
|
|
||||||
|
public @Rule ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
|
||||||
@@ -1807,6 +1812,60 @@ public class MappingMongoConverterUnitTests {
|
|||||||
assertThat(result.shape, is((Shape) sphere));
|
assertThat(result.shape, is((Shape) sphere));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1001
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldWriteCglibProxiedClassTypeInformationCorrectly() {
|
||||||
|
|
||||||
|
ProxyFactory factory = new ProxyFactory();
|
||||||
|
factory.setTargetClass(GenericType.class);
|
||||||
|
factory.setProxyTargetClass(true);
|
||||||
|
|
||||||
|
GenericType<?> proxied = (GenericType<?>) factory.getProxy();
|
||||||
|
BasicDBObject dbo = new BasicDBObject();
|
||||||
|
converter.write(proxied, dbo);
|
||||||
|
|
||||||
|
assertThat(dbo.get("_class"), is((Object) GenericType.class.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1001
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldUseTargetObjectOfLazyLoadingProxyWhenWriting() {
|
||||||
|
|
||||||
|
LazyLoadingProxy mock = mock(LazyLoadingProxy.class);
|
||||||
|
|
||||||
|
BasicDBObject dbo = new BasicDBObject();
|
||||||
|
converter.write(mock, dbo);
|
||||||
|
|
||||||
|
verify(mock, times(1)).initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1034
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void rejectsBasicDbListToBeConvertedIntoComplexType() {
|
||||||
|
|
||||||
|
BasicDBList inner = new BasicDBList();
|
||||||
|
inner.add("key");
|
||||||
|
inner.add("value");
|
||||||
|
|
||||||
|
BasicDBList outer = new BasicDBList();
|
||||||
|
outer.add(inner);
|
||||||
|
outer.add(inner);
|
||||||
|
|
||||||
|
BasicDBObject source = new BasicDBObject("attributes", outer);
|
||||||
|
|
||||||
|
exception.expect(MappingException.class);
|
||||||
|
exception.expectMessage(Item.class.getName());
|
||||||
|
exception.expectMessage(BasicDBList.class.getName());
|
||||||
|
|
||||||
|
converter.read(Item.class, source);
|
||||||
|
}
|
||||||
|
|
||||||
static class GenericType<T> {
|
static class GenericType<T> {
|
||||||
T content;
|
T content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,8 +163,8 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
|
|||||||
new MongoPersistentEntityIndexCreator(mappingContext, factory);
|
new MongoPersistentEntityIndexCreator(mappingContext, factory);
|
||||||
|
|
||||||
assertThat(keysCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("company.address.location", "2d").get()));
|
assertThat(keysCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("company.address.location", "2d").get()));
|
||||||
assertThat(optionsCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("name", "location").add("min", -180)
|
assertThat(optionsCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("name", "company.address.location")
|
||||||
.add("max", 180).add("bits", 26).get()));
|
.add("min", -180).add("max", 180).add("bits", 26).get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,10 +20,13 @@ import static org.hamcrest.collection.IsEmptyCollection.*;
|
|||||||
import static org.hamcrest.core.IsEqual.*;
|
import static org.hamcrest.core.IsEqual.*;
|
||||||
import static org.hamcrest.core.IsInstanceOf.*;
|
import static org.hamcrest.core.IsInstanceOf.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hamcrest.collection.IsEmptyCollection;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Suite;
|
import org.junit.runners.Suite;
|
||||||
@@ -38,6 +41,9 @@ import org.springframework.data.mongodb.core.mapping.DBRef;
|
|||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
import org.springframework.data.mongodb.core.mapping.Field;
|
import org.springframework.data.mongodb.core.mapping.Field;
|
||||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntityTestDummy.MongoPersistentEntityDummyBuilder;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||||
|
|
||||||
import com.mongodb.BasicDBObjectBuilder;
|
import com.mongodb.BasicDBObjectBuilder;
|
||||||
|
|
||||||
@@ -289,9 +295,8 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class);
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class);
|
||||||
|
|
||||||
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||||
assertThat(indexDefinition.getIndexOptions(),
|
assertThat(indexDefinition.getIndexOptions(), equalTo(new BasicDBObjectBuilder().add("name", "compound_index")
|
||||||
equalTo(new BasicDBObjectBuilder().add("name", "compound_index").add("unique", true).add("dropDups", true)
|
.add("unique", true).add("dropDups", true).add("sparse", true).add("background", true).get()));
|
||||||
.add("sparse", true).add("background", true).add("expireAfterSeconds", 10L).get()));
|
|
||||||
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
|
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,9 +309,8 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexDefinedOnSuperClass.class);
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexDefinedOnSuperClass.class);
|
||||||
|
|
||||||
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||||
assertThat(indexDefinition.getIndexOptions(),
|
assertThat(indexDefinition.getIndexOptions(), equalTo(new BasicDBObjectBuilder().add("name", "compound_index")
|
||||||
equalTo(new BasicDBObjectBuilder().add("name", "compound_index").add("unique", true).add("dropDups", true)
|
.add("unique", true).add("dropDups", true).add("sparse", true).add("background", true).get()));
|
||||||
.add("sparse", true).add("background", true).add("expireAfterSeconds", 10L).get()));
|
|
||||||
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
|
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +326,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
assertThat(
|
assertThat(
|
||||||
indexDefinition.getIndexOptions(),
|
indexDefinition.getIndexOptions(),
|
||||||
equalTo(new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).add("sparse", true)
|
equalTo(new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).add("sparse", true)
|
||||||
.add("background", true).add("expireAfterSeconds", 10L).get()));
|
.add("background", true).get()));
|
||||||
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
|
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,6 +368,22 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0));
|
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-963
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void compoundIndexShouldIncludeTTLWhenConsistingOfOnlyOneKey() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexWithOnlyOneKeyAndTTL.class);
|
||||||
|
|
||||||
|
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||||
|
assertThat(
|
||||||
|
indexDefinition.getIndexOptions(),
|
||||||
|
equalTo(new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).add("sparse", true)
|
||||||
|
.add("background", true).add("expireAfterSeconds", 10L).get()));
|
||||||
|
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).get()));
|
||||||
|
}
|
||||||
|
|
||||||
@Document(collection = "CompoundIndexOnLevelOne")
|
@Document(collection = "CompoundIndexOnLevelOne")
|
||||||
static class CompoundIndexOnLevelOne {
|
static class CompoundIndexOnLevelOne {
|
||||||
|
|
||||||
@@ -400,6 +420,13 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
static class ComountIndexWithAutogeneratedName {
|
static class ComountIndexWithAutogeneratedName {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Document(collection = "CompoundIndexWithOnlyOneKeyAndTTL")
|
||||||
|
@CompoundIndex(def = "{'foo': 1}", background = true, dropDups = true, expireAfterSeconds = 10, sparse = true,
|
||||||
|
unique = true)
|
||||||
|
static class CompoundIndexWithOnlyOneKeyAndTTL {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MixedIndexResolutionTests {
|
public static class MixedIndexResolutionTests {
|
||||||
@@ -469,9 +496,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
|
|
||||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class);
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class);
|
||||||
assertIndexPathAndCollection("reference.indexedProperty", "cycleOnLevelOne", indexDefinitions.get(0));
|
assertIndexPathAndCollection("reference.indexedProperty", "cycleOnLevelOne", indexDefinitions.get(0));
|
||||||
assertIndexPathAndCollection("reference.cyclicReference.reference.indexedProperty", "cycleOnLevelOne",
|
assertThat(indexDefinitions, hasSize(1));
|
||||||
indexDefinitions.get(1));
|
|
||||||
assertThat(indexDefinitions, hasSize(2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -488,6 +513,123 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
assertThat(indexDefinitions, hasSize(3));
|
assertThat(indexDefinitions, hasSize(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-949
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldNotDetectCycleInSimilarlyNamedProperties() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(SimilarityHolingBean.class);
|
||||||
|
assertIndexPathAndCollection("norm", "similarityHolingBean", indexDefinitions.get(0));
|
||||||
|
assertThat(indexDefinitions, hasSize(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-962
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldDetectSelfCycleViaCollectionTypeCorrectly() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(SelfCyclingViaCollectionType.class);
|
||||||
|
assertThat(indexDefinitions, IsEmptyCollection.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-962
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldNotDetectCycleWhenTypeIsUsedMoreThanOnce() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(MultipleObjectsOfSameType.class);
|
||||||
|
assertThat(indexDefinitions, IsEmptyCollection.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-962
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldCatchCyclicReferenceExceptionOnRoot() {
|
||||||
|
|
||||||
|
Document documentDummy = new Document() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Annotation> annotationType() {
|
||||||
|
return Document.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String collection() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MongoPersistentProperty propertyMock = mock(MongoPersistentProperty.class);
|
||||||
|
when(propertyMock.isEntity()).thenReturn(true);
|
||||||
|
when(propertyMock.getActualType()).thenThrow(
|
||||||
|
new MongoPersistentEntityIndexResolver.CyclicPropertyReferenceException("foo", Object.class, "bar"));
|
||||||
|
|
||||||
|
MongoPersistentEntity<SelfCyclingViaCollectionType> dummy = MongoPersistentEntityDummyBuilder
|
||||||
|
.forClass(SelfCyclingViaCollectionType.class).withCollection("foo").and(propertyMock)
|
||||||
|
.and(documentDummy).build();
|
||||||
|
|
||||||
|
new MongoPersistentEntityIndexResolver(prepareMappingContext(SelfCyclingViaCollectionType.class))
|
||||||
|
.resolveIndexForEntity(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1025
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIndexFixedOnCollection() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNestedDocumentHavingNamedCompoundIndex.class);
|
||||||
|
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
|
||||||
|
equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1025
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldUseIndexNameForNestedTypesWithNamedCompoundIndexDefinition() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNestedTypeHavingNamedCompoundIndex.class);
|
||||||
|
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
|
||||||
|
equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1025
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedIndexFixedOnCollection() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNestedDocumentHavingNamedIndex.class);
|
||||||
|
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
|
||||||
|
equalTo("propertyOfTypeHavingNamedIndex.property_index"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1025
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldUseIndexNameForNestedTypesWithNamedIndexDefinition() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNestedTypeHavingNamedIndex.class);
|
||||||
|
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
|
||||||
|
equalTo("propertyOfTypeHavingNamedIndex.property_index"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1025
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldUseIndexNameOnRootLevel() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithNamedIndex.class);
|
||||||
|
assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("property_index"));
|
||||||
|
}
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
static class MixedIndexRoot {
|
static class MixedIndexRoot {
|
||||||
|
|
||||||
@@ -554,6 +696,80 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
|
|
||||||
@Indexed String foo;
|
@Indexed String foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
static class SimilarityHolingBean {
|
||||||
|
|
||||||
|
@Indexed @Field("norm") String normalProperty;
|
||||||
|
@Field("similarityL") private List<SimilaritySibling> listOfSimilarilyNamedEntities = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SimilaritySibling {
|
||||||
|
@Field("similarity") private String similarThoughNotEqualNamedProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
static class MultipleObjectsOfSameType {
|
||||||
|
|
||||||
|
SelfCyclingViaCollectionType cycleOne;
|
||||||
|
|
||||||
|
SelfCyclingViaCollectionType cycleTwo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
static class SelfCyclingViaCollectionType {
|
||||||
|
|
||||||
|
List<SelfCyclingViaCollectionType> cyclic;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
@CompoundIndex(name = "c_index", def = "{ foo:1, bar:1 }")
|
||||||
|
static class DocumentWithNamedCompoundIndex {
|
||||||
|
|
||||||
|
String property;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
static class DocumentWithNamedIndex {
|
||||||
|
|
||||||
|
@Indexed(name = "property_index") String property;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TypeWithNamedIndex {
|
||||||
|
|
||||||
|
@Indexed(name = "property_index") String property;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
static class DocumentWithNestedDocumentHavingNamedCompoundIndex {
|
||||||
|
|
||||||
|
DocumentWithNamedCompoundIndex propertyOfTypeHavingNamedCompoundIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CompoundIndex(name = "c_index", def = "{ foo:1, bar:1 }")
|
||||||
|
static class TypeWithNamedCompoundIndex {
|
||||||
|
String property;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
static class DocumentWithNestedTypeHavingNamedCompoundIndex {
|
||||||
|
|
||||||
|
TypeWithNamedCompoundIndex propertyOfTypeHavingNamedCompoundIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
static class DocumentWithNestedDocumentHavingNamedIndex {
|
||||||
|
|
||||||
|
DocumentWithNamedIndex propertyOfTypeHavingNamedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document
|
||||||
|
static class DocumentWithNestedTypeHavingNamedIndex {
|
||||||
|
|
||||||
|
TypeWithNamedIndex propertyOfTypeHavingNamedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {
|
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {
|
||||||
@@ -586,4 +802,5 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
}
|
}
|
||||||
assertThat(holder.getCollection(), equalTo(expectedCollection));
|
assertThat(holder.getCollection(), equalTo(expectedCollection));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 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.index;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.CycleGuard.Path;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link Path}.
|
||||||
|
*
|
||||||
|
* @author Christoph Strobl
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class PathUnitTests {
|
||||||
|
|
||||||
|
@Mock MongoPersistentEntity<?> entityMock;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
public void setUp() {
|
||||||
|
when(entityMock.getType()).thenReturn((Class) Object.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-962
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldIdentifyCycleForOwnerOfSameTypeAndMatchingPath() {
|
||||||
|
|
||||||
|
MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo");
|
||||||
|
assertThat(new Path(property, "foo.bar").cycles(property, "foo.bar.bar"), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-962
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public void shouldAllowMatchingPathForDifferentOwners() {
|
||||||
|
|
||||||
|
MongoPersistentProperty existing = createPersistentPropertyMock(entityMock, "foo");
|
||||||
|
|
||||||
|
MongoPersistentEntity entityOfDifferentType = Mockito.mock(MongoPersistentEntity.class);
|
||||||
|
when(entityOfDifferentType.getType()).thenReturn(String.class);
|
||||||
|
MongoPersistentProperty toBeVerified = createPersistentPropertyMock(entityOfDifferentType, "foo");
|
||||||
|
|
||||||
|
assertThat(new Path(existing, "foo.bar").cycles(toBeVerified, "foo.bar.bar"), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-962
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldAllowEqaulPropertiesOnDifferentPaths() {
|
||||||
|
|
||||||
|
MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo");
|
||||||
|
assertThat(new Path(property, "foo.bar").cycles(property, "foo2.bar.bar"), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
private MongoPersistentProperty createPersistentPropertyMock(MongoPersistentEntity owner, String fieldname) {
|
||||||
|
|
||||||
|
MongoPersistentProperty property = Mockito.mock(MongoPersistentProperty.class);
|
||||||
|
when(property.getOwner()).thenReturn(owner);
|
||||||
|
when(property.getFieldName()).thenReturn(fieldname);
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 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.mapping;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.annotation.Version;
|
||||||
|
import org.springframework.data.mapping.AssociationHandler;
|
||||||
|
import org.springframework.data.mapping.PersistentProperty;
|
||||||
|
import org.springframework.data.mapping.PreferredConstructor;
|
||||||
|
import org.springframework.data.mapping.PropertyHandler;
|
||||||
|
import org.springframework.data.mapping.SimpleAssociationHandler;
|
||||||
|
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||||
|
import org.springframework.data.util.TypeInformation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trivial dummy implementation of {@link MongoPersistentEntity} to be used in tests.
|
||||||
|
*
|
||||||
|
* @author Christoph Strobl
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public class MongoPersistentEntityTestDummy<T> implements MongoPersistentEntity<T> {
|
||||||
|
|
||||||
|
private Map<Class<?>, Annotation> annotations = new HashMap<Class<?>, Annotation>();
|
||||||
|
private Collection<MongoPersistentProperty> properties = new ArrayList<MongoPersistentProperty>();
|
||||||
|
private String collection;
|
||||||
|
private String name;
|
||||||
|
private Class<T> type;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferredConstructor<T, MongoPersistentProperty> getPersistenceConstructor() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConstructorArgument(PersistentProperty<?> property) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isIdProperty(PersistentProperty<?> property) {
|
||||||
|
return property != null ? property.isIdProperty() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVersionProperty(PersistentProperty<?> property) {
|
||||||
|
return property != null ? property.isIdProperty() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MongoPersistentProperty getIdProperty() {
|
||||||
|
return getPersistentProperty(Id.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MongoPersistentProperty getVersionProperty() {
|
||||||
|
return getPersistentProperty(Version.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MongoPersistentProperty getPersistentProperty(String name) {
|
||||||
|
|
||||||
|
for (MongoPersistentProperty p : this.properties) {
|
||||||
|
if (p.getName().equals(name)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MongoPersistentProperty getPersistentProperty(Class<? extends Annotation> annotationType) {
|
||||||
|
|
||||||
|
for (MongoPersistentProperty p : this.properties) {
|
||||||
|
if (p.isAnnotationPresent(annotationType)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasIdProperty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasVersionProperty() {
|
||||||
|
return getVersionProperty() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<T> getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getTypeAlias() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TypeInformation<T> getTypeInformation() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doWithProperties(PropertyHandler<MongoPersistentProperty> handler) {
|
||||||
|
|
||||||
|
for (MongoPersistentProperty p : this.properties) {
|
||||||
|
handler.doWithPersistentProperty(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doWithProperties(SimplePropertyHandler handler) {
|
||||||
|
|
||||||
|
for (MongoPersistentProperty p : this.properties) {
|
||||||
|
handler.doWithPersistentProperty(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doWithAssociations(AssociationHandler<MongoPersistentProperty> handler) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doWithAssociations(SimpleAssociationHandler handler) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
|
||||||
|
return (A) this.annotations.get(annotationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCollection() {
|
||||||
|
return this.collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple builder to create {@link MongoPersistentEntityTestDummy} with defined properties.
|
||||||
|
*
|
||||||
|
* @author Christoph Strobl
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public static class MongoPersistentEntityDummyBuilder<T> {
|
||||||
|
|
||||||
|
private MongoPersistentEntityTestDummy<T> instance;
|
||||||
|
|
||||||
|
private MongoPersistentEntityDummyBuilder(Class<T> type) {
|
||||||
|
this.instance = new MongoPersistentEntityTestDummy<T>();
|
||||||
|
this.instance.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
public static <T> MongoPersistentEntityDummyBuilder<T> forClass(Class<T> type) {
|
||||||
|
return new MongoPersistentEntityDummyBuilder(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MongoPersistentEntityDummyBuilder<T> withName(String name) {
|
||||||
|
this.instance.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MongoPersistentEntityDummyBuilder<T> and(MongoPersistentProperty property) {
|
||||||
|
this.instance.properties.add(property);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MongoPersistentEntityDummyBuilder<T> withCollection(String collection) {
|
||||||
|
this.instance.collection = collection;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MongoPersistentEntityDummyBuilder<T> and(Annotation annotation) {
|
||||||
|
this.instance.annotations.put(annotation.annotationType(), annotation);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MongoPersistentEntityTestDummy<T> build() {
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011 the original author or authors.
|
* Copyright 2011-2014 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -32,6 +32,7 @@ import org.junit.Ignore;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.geo.Box;
|
||||||
import org.springframework.data.mongodb.MongoDbFactory;
|
import org.springframework.data.mongodb.MongoDbFactory;
|
||||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||||
@@ -50,6 +51,7 @@ import com.mongodb.Mongo;
|
|||||||
* Integration test for {@link MongoTemplate}'s Map-Reduce operations
|
* Integration test for {@link MongoTemplate}'s Map-Reduce operations
|
||||||
*
|
*
|
||||||
* @author Mark Pollack
|
* @author Mark Pollack
|
||||||
|
* @author Thomas Darimont
|
||||||
*/
|
*/
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
@ContextConfiguration("classpath:infrastructure.xml")
|
@ContextConfiguration("classpath:infrastructure.xml")
|
||||||
@@ -276,6 +278,31 @@ public class MapReduceTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-938
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void mapReduceShouldUseQueryMapper() {
|
||||||
|
|
||||||
|
DBCollection c = mongoTemplate.getDb().getCollection("jmrWithGeo");
|
||||||
|
|
||||||
|
c.save(new BasicDBObject("x", new String[] { "a", "b" }).append("loc", new double[] { 0, 0 }));
|
||||||
|
c.save(new BasicDBObject("x", new String[] { "b", "c" }).append("loc", new double[] { 0, 0 }));
|
||||||
|
c.save(new BasicDBObject("x", new String[] { "c", "d" }).append("loc", new double[] { 0, 0 }));
|
||||||
|
|
||||||
|
Query query = new Query(where("x").ne(new String[] { "a", "b" }).and("loc")
|
||||||
|
.within(new Box(new double[] { 0, 0 }, new double[] { 1, 1 })));
|
||||||
|
|
||||||
|
MapReduceResults<ValueObject> results = template.mapReduce(query, "jmrWithGeo", mapFunction, reduceFunction,
|
||||||
|
ValueObject.class);
|
||||||
|
|
||||||
|
Map<String, Float> m = copyToMap(results);
|
||||||
|
assertEquals(3, m.size());
|
||||||
|
assertEquals(1, m.get("b").intValue());
|
||||||
|
assertEquals(2, m.get("c").intValue());
|
||||||
|
assertEquals(1, m.get("d").intValue());
|
||||||
|
}
|
||||||
|
|
||||||
private void performMapReduce(boolean inline, boolean withQuery) {
|
private void performMapReduce(boolean inline, boolean withQuery) {
|
||||||
createMapReduceData();
|
createMapReduceData();
|
||||||
MapReduceResults<ValueObject> results;
|
MapReduceResults<ValueObject> results;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import static org.junit.Assert.*;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,6 +31,7 @@ import org.junit.Test;
|
|||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
* @author Becca Gaspard
|
* @author Becca Gaspard
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Thomas Darimont
|
||||||
*/
|
*/
|
||||||
public class UpdateTests {
|
public class UpdateTests {
|
||||||
|
|
||||||
@@ -284,4 +286,71 @@ public class UpdateTests {
|
|||||||
public void testCreatingUpdateWithNullKeyThrowsException() {
|
public void testCreatingUpdateWithNullKeyThrowsException() {
|
||||||
Update.update(null, "value");
|
Update.update(null, "value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-953
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testEquality() {
|
||||||
|
|
||||||
|
Update actualUpdate = new Update() //
|
||||||
|
.inc("size", 1) //
|
||||||
|
.set("nl", null) //
|
||||||
|
.set("directory", "/Users/Test/Desktop") //
|
||||||
|
.push("authors", Collections.singletonMap("name", "Sven")) //
|
||||||
|
.pop("authors", Update.Position.FIRST) //
|
||||||
|
.set("foo", "bar");
|
||||||
|
|
||||||
|
Update expectedUpdate = new Update() //
|
||||||
|
.inc("size", 1) //
|
||||||
|
.set("nl", null) //
|
||||||
|
.set("directory", "/Users/Test/Desktop") //
|
||||||
|
.push("authors", Collections.singletonMap("name", "Sven")) //
|
||||||
|
.pop("authors", Update.Position.FIRST) //
|
||||||
|
.set("foo", "bar");
|
||||||
|
|
||||||
|
assertThat(actualUpdate, is(equalTo(actualUpdate)));
|
||||||
|
assertThat(actualUpdate.hashCode(), is(equalTo(actualUpdate.hashCode())));
|
||||||
|
assertThat(actualUpdate, is(equalTo(expectedUpdate)));
|
||||||
|
assertThat(actualUpdate.hashCode(), is(equalTo(expectedUpdate.hashCode())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-953
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testToString() {
|
||||||
|
|
||||||
|
Update actualUpdate = new Update() //
|
||||||
|
.inc("size", 1) //
|
||||||
|
.set("nl", null) //
|
||||||
|
.set("directory", "/Users/Test/Desktop") //
|
||||||
|
.push("authors", Collections.singletonMap("name", "Sven")) //
|
||||||
|
.pop("authors", Update.Position.FIRST) //
|
||||||
|
.set("foo", "bar");
|
||||||
|
|
||||||
|
Update expectedUpdate = new Update() //
|
||||||
|
.inc("size", 1) //
|
||||||
|
.set("nl", null) //
|
||||||
|
.set("directory", "/Users/Test/Desktop") //
|
||||||
|
.push("authors", Collections.singletonMap("name", "Sven")) //
|
||||||
|
.pop("authors", Update.Position.FIRST) //
|
||||||
|
.set("foo", "bar");
|
||||||
|
|
||||||
|
assertThat(actualUpdate.toString(), is(equalTo(expectedUpdate.toString())));
|
||||||
|
assertThat(actualUpdate.toString(), is("{ \"$inc\" : { \"size\" : 1} ," //
|
||||||
|
+ " \"$set\" : { \"nl\" : null , \"directory\" : \"/Users/Test/Desktop\" , \"foo\" : \"bar\"} , " //
|
||||||
|
+ "\"$push\" : { \"authors\" : { \"name\" : \"Sven\"}} " //
|
||||||
|
+ ", \"$pop\" : { \"authors\" : -1}}")); //
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1002
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void toStringWorksForUpdateWithComplexObject() {
|
||||||
|
|
||||||
|
Update update = new Update().addToSet("key", new DateTime());
|
||||||
|
assertThat(update.toString(), is(notNullValue()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -880,4 +881,86 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
|||||||
|
|
||||||
assertThat(result.getContent(), hasSize(1));
|
assertThat(result.getContent(), hasSize(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-745
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void findByCustomQueryFirstnamesInListAndLastname() {
|
||||||
|
|
||||||
|
repository.save(new Person("foo", "bar"));
|
||||||
|
repository.save(new Person("bar", "bar"));
|
||||||
|
repository.save(new Person("fuu", "bar"));
|
||||||
|
repository.save(new Person("notfound", "bar"));
|
||||||
|
|
||||||
|
Page<Person> result = repository.findByCustomQueryFirstnamesAndLastname(Arrays.asList("bar", "foo", "fuu"), "bar",
|
||||||
|
new PageRequest(0, 2));
|
||||||
|
|
||||||
|
assertThat(result.getContent(), hasSize(2));
|
||||||
|
assertThat(result.getTotalPages(), is(2));
|
||||||
|
assertThat(result.getTotalElements(), is(3L));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-745
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void findByCustomQueryLastnameAndStreetInList() {
|
||||||
|
|
||||||
|
repository.save(new Person("foo", "bar").withAddress(new Address("street1", "1", "SB")));
|
||||||
|
repository.save(new Person("bar", "bar").withAddress(new Address("street2", "1", "SB")));
|
||||||
|
repository.save(new Person("fuu", "bar").withAddress(new Address("street1", "2", "RGB")));
|
||||||
|
repository.save(new Person("notfound", "notfound"));
|
||||||
|
|
||||||
|
Page<Person> result = repository.findByCustomQueryLastnameAndAddressStreetInList("bar",
|
||||||
|
Arrays.asList("street1", "street2"), new PageRequest(0, 2));
|
||||||
|
|
||||||
|
assertThat(result.getContent(), hasSize(2));
|
||||||
|
assertThat(result.getTotalPages(), is(2));
|
||||||
|
assertThat(result.getTotalElements(), is(3L));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignored for now as this requires Querydsl 3.4.1 to succeed.
|
||||||
|
*
|
||||||
|
* @see DATAMONGO-972
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
public void shouldExecuteFindOnDbRefCorrectly() {
|
||||||
|
|
||||||
|
operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class);
|
||||||
|
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername("Valerie Matthews");
|
||||||
|
|
||||||
|
operations.save(user);
|
||||||
|
|
||||||
|
dave.setCreator(user);
|
||||||
|
operations.save(dave);
|
||||||
|
|
||||||
|
assertThat(repository.findOne(QPerson.person.creator.eq(user)), is(dave));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-969
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldFindPersonsWhenUsingQueryDslPerdicatedOnIdProperty() {
|
||||||
|
assertThat(repository.findAll(person.id.in(Arrays.asList(dave.id, carter.id))), containsInAnyOrder(dave, carter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1030
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void executesSingleEntityQueryWithProjectionCorrectly() {
|
||||||
|
|
||||||
|
PersonSummary result = repository.findSummaryByLastname("Beauford");
|
||||||
|
|
||||||
|
assertThat(result, is(notNullValue()));
|
||||||
|
assertThat(result.firstname, is("Carter"));
|
||||||
|
assertThat(result.lastname, is("Beauford"));
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,6 +261,16 @@ public class Person extends Contact {
|
|||||||
return this.getId().equals(that.getId());
|
return this.getId().equals(that.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Person withAddress(Address address) {
|
||||||
|
|
||||||
|
this.address = address;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreator(User creator) {
|
||||||
|
this.creator = creator;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -290,4 +290,21 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
|||||||
* @see DATAMONGO-893
|
* @see DATAMONGO-893
|
||||||
*/
|
*/
|
||||||
Page<Person> findByAddressIn(List<Address> address, Pageable page);
|
Page<Person> findByAddressIn(List<Address> address, Pageable page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-745
|
||||||
|
*/
|
||||||
|
@Query("{firstname:{$in:?0}, lastname:?1}")
|
||||||
|
Page<Person> findByCustomQueryFirstnamesAndLastname(List<String> firstnames, String lastname, Pageable page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-745
|
||||||
|
*/
|
||||||
|
@Query("{lastname:?0, address.street:{$in:?1}}")
|
||||||
|
Page<Person> findByCustomQueryLastnameAndAddressStreetInList(String lastname, List<String> streetNames, Pageable page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-1030
|
||||||
|
*/
|
||||||
|
PersonSummary findSummaryByLastname(String lastname);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.mongodb.repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Oliver Gierke
|
||||||
|
*/
|
||||||
|
public class PersonSummary {
|
||||||
|
|
||||||
|
String firstname;
|
||||||
|
String lastname;
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@ import com.mongodb.WriteResult;
|
|||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class AbstracMongoQueryUnitTests {
|
public class AbstractMongoQueryUnitTests {
|
||||||
|
|
||||||
@Mock RepositoryMetadata metadataMock;
|
@Mock RepositoryMetadata metadataMock;
|
||||||
@Mock MongoOperations mongoOperationsMock;
|
@Mock MongoOperations mongoOperationsMock;
|
||||||
@@ -88,7 +88,8 @@ public class AbstracMongoQueryUnitTests {
|
|||||||
|
|
||||||
createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" });
|
createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" });
|
||||||
|
|
||||||
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), Matchers.eq("persons"));
|
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), Matchers.eq(Person.class),
|
||||||
|
Matchers.eq("persons"));
|
||||||
verify(this.mongoOperationsMock, times(0)).find(Matchers.any(Query.class), Matchers.any(Class.class),
|
verify(this.mongoOperationsMock, times(0)).find(Matchers.any(Query.class), Matchers.any(Class.class),
|
||||||
Matchers.anyString());
|
Matchers.anyString());
|
||||||
}
|
}
|
||||||
@@ -122,19 +123,21 @@ public class AbstracMongoQueryUnitTests {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @see DATAMONGO-566
|
* @see DATAMONGO-566
|
||||||
|
* @see DATAMONGO-978
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() {
|
public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() {
|
||||||
|
|
||||||
when(writeResultMock.getN()).thenReturn(100);
|
when(writeResultMock.getN()).thenReturn(100);
|
||||||
when(this.mongoOperationsMock.remove(Matchers.any(Query.class), Matchers.eq("persons")))
|
when(this.mongoOperationsMock.remove(Matchers.any(Query.class), Matchers.eq(Person.class), Matchers.eq("persons")))
|
||||||
.thenReturn(writeResultMock);
|
.thenReturn(writeResultMock);
|
||||||
|
|
||||||
MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class);
|
MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class);
|
||||||
query.setDeleteQuery(true);
|
query.setDeleteQuery(true);
|
||||||
|
|
||||||
assertThat(query.execute(new Object[] { "fake" }), is((Object) 100L));
|
assertThat(query.execute(new Object[] { "fake" }), is((Object) 100L));
|
||||||
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), Matchers.eq("persons"));
|
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), Matchers.eq(Person.class),
|
||||||
|
Matchers.eq("persons"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private MongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
private MongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.mongodb.repository.query;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Matchers;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import org.springframework.data.mongodb.MongoDbFactory;
|
||||||
|
import org.springframework.data.mongodb.core.MongoOperations;
|
||||||
|
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||||
|
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||||
|
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||||
|
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||||
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
import org.springframework.data.mongodb.repository.Person;
|
||||||
|
import org.springframework.data.mongodb.repository.Query;
|
||||||
|
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBObjectBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link PartTreeMongoQuery}.
|
||||||
|
*
|
||||||
|
* @author Christoph Strobl
|
||||||
|
* @author Oliver Gierke
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class PartTreeMongoQueryUnitTests {
|
||||||
|
|
||||||
|
@Mock RepositoryMetadata metadataMock;
|
||||||
|
@Mock MongoOperations mongoOperationsMock;
|
||||||
|
|
||||||
|
MongoMappingContext mappingContext;
|
||||||
|
|
||||||
|
public @Rule ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
public void setUp() {
|
||||||
|
|
||||||
|
when(metadataMock.getDomainType()).thenReturn((Class) Person.class);
|
||||||
|
when(metadataMock.getReturnedDomainClass(Matchers.any(Method.class))).thenReturn((Class) Person.class);
|
||||||
|
mappingContext = new MongoMappingContext();
|
||||||
|
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDbFactory.class));
|
||||||
|
MongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
|
||||||
|
|
||||||
|
when(mongoOperationsMock.getConverter()).thenReturn(converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMOGO-952
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void rejectsInvalidFieldSpecification() {
|
||||||
|
|
||||||
|
exception.expect(IllegalStateException.class);
|
||||||
|
exception.expectMessage("findByLastname");
|
||||||
|
|
||||||
|
deriveQueryFromMethod("findByLastname", new Object[] { "foo" });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMOGO-952
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void singleFieldJsonIncludeRestrictionShouldBeConsidered() {
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstname",
|
||||||
|
new Object[] { "foo" });
|
||||||
|
|
||||||
|
assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 1).get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMOGO-952
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void multiFieldJsonIncludeRestrictionShouldBeConsidered() {
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstnameAndLastname",
|
||||||
|
new Object[] { "foo", "bar" });
|
||||||
|
|
||||||
|
assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 1).add("lastname", 1).get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMOGO-952
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void multiFieldJsonExcludeRestrictionShouldBeConsidered() {
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstnameAndLastname",
|
||||||
|
new Object[] { "foo", "bar" });
|
||||||
|
|
||||||
|
assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 0).add("lastname", 0).get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object[] args) {
|
||||||
|
|
||||||
|
Class<?>[] types = new Class<?>[args.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
types[i] = args[i].getClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
PartTreeMongoQuery partTreeQuery = createQueryForMethod(method, types);
|
||||||
|
|
||||||
|
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(partTreeQuery.getQueryMethod(), args);
|
||||||
|
return partTreeQuery.createQuery(new ConvertingParameterAccessor(mongoOperationsMock.getConverter(), accessor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PartTreeMongoQuery createQueryForMethod(String methodName, Class<?>... paramTypes) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Method method = Repo.class.getMethod(methodName, paramTypes);
|
||||||
|
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadataMock, mappingContext);
|
||||||
|
|
||||||
|
return new PartTreeMongoQuery(queryMethod, mongoOperationsMock);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new IllegalArgumentException(e.getMessage(), e);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new IllegalArgumentException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Repo extends MongoRepository<Person, Long> {
|
||||||
|
|
||||||
|
@Query(fields = "firstname")
|
||||||
|
Person findByLastname(String lastname);
|
||||||
|
|
||||||
|
@Query(fields = "{ 'firstname' : 1 }")
|
||||||
|
Person findByFirstname(String lastname);
|
||||||
|
|
||||||
|
@Query(fields = "{ 'firstname' : 1, 'lastname' : 1 }")
|
||||||
|
Person findByFirstnameAndLastname(String firstname, String lastname);
|
||||||
|
|
||||||
|
@Query(fields = "{ 'firstname' : 0, 'lastname' : 0 }")
|
||||||
|
Person findPersonByFirstnameAndLastname(String firstname, String lastname);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,9 @@ import static org.junit.Assert.*;
|
|||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -46,6 +49,7 @@ import com.mongodb.DBObject;
|
|||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Thomas Darimont
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class StringBasedMongoQueryUnitTests {
|
public class StringBasedMongoQueryUnitTests {
|
||||||
@@ -158,6 +162,99 @@ public class StringBasedMongoQueryUnitTests {
|
|||||||
createQueryForMethod("invalidMethod", String.class);
|
createQueryForMethod("invalidMethod", String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-420
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception {
|
||||||
|
|
||||||
|
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {
|
||||||
|
new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1) });
|
||||||
|
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields", DBObject.class,
|
||||||
|
Map.class);
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
|
||||||
|
|
||||||
|
assertThat(query.getQueryObject(),
|
||||||
|
is(new BasicQuery("{ \"firstname\": \"first\", \"lastname\": \"last\"}").getQueryObject()));
|
||||||
|
assertThat(query.getFieldsObject(), is(new BasicQuery(null, "{ \"lastname\": 1}").getFieldsObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-420
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQuoting() throws Exception {
|
||||||
|
|
||||||
|
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { "fun" });
|
||||||
|
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByTitleBeginsWithExplicitQuoting", String.class);
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
|
||||||
|
|
||||||
|
assertThat(query.getQueryObject(), is(new BasicQuery("{title: {$regex: '^fun', $options: 'i'}}").getQueryObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-995, DATAMONGO-420
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldParseQueryWithParametersInExpression() throws Exception {
|
||||||
|
|
||||||
|
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { 1, 2, 3, 4 });
|
||||||
|
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithParametersInExpression", int.class,
|
||||||
|
int.class, int.class, int.class);
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
|
||||||
|
|
||||||
|
assertThat(query.getQueryObject(), is(new BasicQuery(
|
||||||
|
"{$where: 'return this.date.getUTCMonth() == 3 && this.date.getUTCDay() == 4;'}").getQueryObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-995, DATAMONGO-420
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception {
|
||||||
|
|
||||||
|
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews");
|
||||||
|
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class);
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
|
||||||
|
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}");
|
||||||
|
|
||||||
|
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-995, DATAMONGO-420
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exception {
|
||||||
|
|
||||||
|
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*");
|
||||||
|
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class);
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
|
||||||
|
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}");
|
||||||
|
|
||||||
|
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-995, DATAMONGO-420
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void bindsSimplePropertyWithRegexCorrectly() throws Exception {
|
||||||
|
|
||||||
|
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class);
|
||||||
|
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*");
|
||||||
|
|
||||||
|
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
|
||||||
|
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}");
|
||||||
|
|
||||||
|
assertThat(query.getQueryObject(), is(reference.getQueryObject()));
|
||||||
|
}
|
||||||
|
|
||||||
private StringBasedMongoQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
|
private StringBasedMongoQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
|
||||||
|
|
||||||
Method method = SampleRepository.class.getMethod(name, parameters);
|
Method method = SampleRepository.class.getMethod(name, parameters);
|
||||||
@@ -170,6 +267,9 @@ public class StringBasedMongoQueryUnitTests {
|
|||||||
@Query("{ 'lastname' : ?0 }")
|
@Query("{ 'lastname' : ?0 }")
|
||||||
Person findByLastname(String lastname);
|
Person findByLastname(String lastname);
|
||||||
|
|
||||||
|
@Query("{ 'lastname' : '?0' }")
|
||||||
|
Person findByLastnameQuoted(String lastname);
|
||||||
|
|
||||||
@Query("{ 'address' : ?0 }")
|
@Query("{ 'address' : ?0 }")
|
||||||
Person findByAddress(Address address);
|
Person findByAddress(Address address);
|
||||||
|
|
||||||
@@ -184,5 +284,14 @@ public class StringBasedMongoQueryUnitTests {
|
|||||||
|
|
||||||
@Query(value = "{ 'lastname' : ?0 }", delete = true, count = true)
|
@Query(value = "{ 'lastname' : ?0 }", delete = true, count = true)
|
||||||
void invalidMethod(String lastname);
|
void invalidMethod(String lastname);
|
||||||
|
|
||||||
|
@Query(value = "?0", fields = "?1")
|
||||||
|
DBObject findByParameterizedCriteriaAndFields(DBObject criteria, Map<String, Integer> fields);
|
||||||
|
|
||||||
|
@Query("{'title': { $regex : '^?0', $options : 'i'}}")
|
||||||
|
List<DBObject> findByTitleBeginsWithExplicitQuoting(String title);
|
||||||
|
|
||||||
|
@Query(value = "{$where: 'return this.date.getUTCMonth() == ?2 && this.date.getUTCDay() == ?3;'}")
|
||||||
|
List<DBObject> findByQueryWithParametersInExpression(int param1, int param2, int param3, int param4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011-2013 the original author or authors.
|
* Copyright 2011-2014 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,8 +17,10 @@ package org.springframework.data.mongodb.repository.support;
|
|||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
|
||||||
|
|
||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -32,6 +34,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
|||||||
import org.springframework.data.mongodb.repository.QAddress;
|
import org.springframework.data.mongodb.repository.QAddress;
|
||||||
import org.springframework.data.mongodb.repository.QPerson;
|
import org.springframework.data.mongodb.repository.QPerson;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBList;
|
||||||
import com.mongodb.BasicDBObject;
|
import com.mongodb.BasicDBObject;
|
||||||
import com.mongodb.DBObject;
|
import com.mongodb.DBObject;
|
||||||
import com.mysema.query.types.expr.BooleanOperation;
|
import com.mysema.query.types.expr.BooleanOperation;
|
||||||
@@ -43,6 +46,7 @@ import com.mysema.query.types.path.StringPath;
|
|||||||
* Unit tests for {@link SpringDataMongodbSerializer}.
|
* Unit tests for {@link SpringDataMongodbSerializer}.
|
||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Christoph Strobl
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class SpringDataMongodbSerializerUnitTests {
|
public class SpringDataMongodbSerializerUnitTests {
|
||||||
@@ -132,6 +136,41 @@ public class SpringDataMongodbSerializerUnitTests {
|
|||||||
assertThat(path, is("0"));
|
assertThat(path, is("0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-969
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldConvertObjectIdEvenWhenNestedInOperatorDbObject() {
|
||||||
|
|
||||||
|
ObjectId value = new ObjectId("53bb9fd14438765b29c2d56e");
|
||||||
|
DBObject serialized = serializer.asDBObject("_id", new BasicDBObject("$ne", value.toString()));
|
||||||
|
|
||||||
|
DBObject _id = getAsDBObject(serialized, "_id");
|
||||||
|
ObjectId $ne = getTypedValue(_id, "$ne", ObjectId.class);
|
||||||
|
assertThat($ne, is(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-969
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldConvertCollectionOfObjectIdEvenWhenNestedInOperatorDbObject() {
|
||||||
|
|
||||||
|
ObjectId firstId = new ObjectId("53bb9fd14438765b29c2d56e");
|
||||||
|
ObjectId secondId = new ObjectId("53bb9fda4438765b29c2d56f");
|
||||||
|
|
||||||
|
BasicDBList objectIds = new BasicDBList();
|
||||||
|
objectIds.add(firstId.toString());
|
||||||
|
objectIds.add(secondId.toString());
|
||||||
|
|
||||||
|
DBObject serialized = serializer.asDBObject("_id", new BasicDBObject("$in", objectIds));
|
||||||
|
|
||||||
|
DBObject _id = getAsDBObject(serialized, "_id");
|
||||||
|
Object[] $in = getTypedValue(_id, "$in", Object[].class);
|
||||||
|
|
||||||
|
assertThat($in, Matchers.<Object> arrayContaining(firstId, secondId));
|
||||||
|
}
|
||||||
|
|
||||||
class Address {
|
class Address {
|
||||||
String id;
|
String id;
|
||||||
String street;
|
String street;
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 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.test.util;
|
||||||
|
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.rules.TestRule;
|
||||||
|
import org.junit.runner.Description;
|
||||||
|
import org.junit.runners.model.Statement;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import com.mongodb.DB;
|
||||||
|
import com.mongodb.MongoClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christoph Strobl
|
||||||
|
*/
|
||||||
|
public class CleanMongoDB implements TestRule {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CleanMongoDB.class);
|
||||||
|
|
||||||
|
public enum Types {
|
||||||
|
DATABASE, COLLECTION, INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> preserveDatabases = new HashSet<String>() {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -8698807376808700046L;
|
||||||
|
|
||||||
|
{
|
||||||
|
add("admin");
|
||||||
|
add("local");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private Set<String> dbNames = new HashSet<String>();
|
||||||
|
private Set<String> collectionNames = new HashSet<String>();
|
||||||
|
private Set<Types> types = new HashSet<CleanMongoDB.Types>();
|
||||||
|
private MongoClient client;
|
||||||
|
|
||||||
|
public CleanMongoDB() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CleanMongoDB(String host, int port) throws UnknownHostException {
|
||||||
|
this(new MongoClient(host, port));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CleanMongoDB(MongoClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CleanMongoDB everything() {
|
||||||
|
|
||||||
|
CleanMongoDB cleanMongoDB = new CleanMongoDB();
|
||||||
|
cleanMongoDB.clean(Types.DATABASE);
|
||||||
|
return cleanMongoDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CleanMongoDB databases(String... dbNames) {
|
||||||
|
|
||||||
|
CleanMongoDB cleanMongoDB = new CleanMongoDB();
|
||||||
|
cleanMongoDB.clean(Types.DATABASE);
|
||||||
|
cleanMongoDB.collectionNames.addAll(Arrays.asList(dbNames));
|
||||||
|
return cleanMongoDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CleanMongoDB indexes() {
|
||||||
|
|
||||||
|
CleanMongoDB cleanMongoDB = new CleanMongoDB();
|
||||||
|
cleanMongoDB.clean(Types.INDEX);
|
||||||
|
return cleanMongoDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CleanMongoDB clean(Types... types) {
|
||||||
|
|
||||||
|
this.types.addAll(Arrays.asList(types));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Statement apply() {
|
||||||
|
return apply(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Statement apply(Statement base, Description description) {
|
||||||
|
return new MongoCleanStatement(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MongoCleanStatement extends Statement {
|
||||||
|
|
||||||
|
private final Statement base;
|
||||||
|
|
||||||
|
public MongoCleanStatement(Statement base) {
|
||||||
|
this.base = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evaluate() throws Throwable {
|
||||||
|
|
||||||
|
if (base != null) {
|
||||||
|
base.evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isInternal = false;
|
||||||
|
if (client == null) {
|
||||||
|
client = new MongoClient();
|
||||||
|
isInternal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<String> dbNamesToUse = dbNames;
|
||||||
|
if (dbNamesToUse.isEmpty()) {
|
||||||
|
dbNamesToUse = client.getDatabaseNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String dbName : dbNamesToUse) {
|
||||||
|
|
||||||
|
if (preserveDatabases.contains(dbName.toLowerCase())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.contains(Types.DATABASE)) {
|
||||||
|
client.dropDatabase(dbName);
|
||||||
|
LOGGER.debug("Dropping DB '{}'. ", dbName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.contains(Types.COLLECTION)) {
|
||||||
|
|
||||||
|
DB db = client.getDB(dbName);
|
||||||
|
Collection<String> collectionsToUse = initCollectionNames(db);
|
||||||
|
for (String collectionName : collectionsToUse) {
|
||||||
|
if (db.collectionExists(collectionName)) {
|
||||||
|
db.getCollectionFromString(collectionName).drop();
|
||||||
|
LOGGER.debug("Dropping collection '{}' for DB '{}'. ", collectionName, dbName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.contains(Types.INDEX)) {
|
||||||
|
|
||||||
|
DB db = client.getDB(dbName);
|
||||||
|
Collection<String> collectionsToUse = initCollectionNames(db);
|
||||||
|
for (String collectionName : collectionsToUse) {
|
||||||
|
if (db.collectionExists(collectionName)) {
|
||||||
|
db.getCollectionFromString(collectionName).dropIndexes();
|
||||||
|
LOGGER.debug("Dropping indexes in collection '{}' for DB '{}'. ", collectionName, dbName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInternal) {
|
||||||
|
client.close();
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<String> initCollectionNames(DB db) {
|
||||||
|
|
||||||
|
Collection<String> collectionsToUse = collectionNames;
|
||||||
|
if (CollectionUtils.isEmpty(collectionsToUse)) {
|
||||||
|
collectionsToUse = db.getCollectionNames();
|
||||||
|
}
|
||||||
|
return collectionsToUse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 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.test.util;
|
||||||
|
|
||||||
|
import org.junit.runner.Result;
|
||||||
|
import org.junit.runner.notification.RunListener;
|
||||||
|
import org.springframework.data.mongodb.test.util.CleanMongoDB.Types;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christoph Strobl
|
||||||
|
*/
|
||||||
|
public class CleanMongoDBJunitRunListener extends RunListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void testRunFinished(Result result) throws Exception {
|
||||||
|
super.testRunFinished(result);
|
||||||
|
try {
|
||||||
|
new CleanMongoDB().clean(Types.INDEX).apply().evaluate();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 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.test.util;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.Description;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.model.Statement;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import org.springframework.data.mongodb.test.util.CleanMongoDB.Types;
|
||||||
|
|
||||||
|
import com.mongodb.DB;
|
||||||
|
import com.mongodb.DBCollection;
|
||||||
|
import com.mongodb.MongoClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christoph Strobl
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class CleanMongoDBTests {
|
||||||
|
|
||||||
|
private CleanMongoDB cleaner;
|
||||||
|
private @Mock Statement baseStatementMock;
|
||||||
|
private @Mock Description descriptionMock;
|
||||||
|
private @Mock MongoClient mongoClientMock;
|
||||||
|
private @Mock DB db1mock;
|
||||||
|
private @Mock DB db2mock;
|
||||||
|
private @Mock DBCollection collection1mock;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
|
||||||
|
when(mongoClientMock.getDatabaseNames()).thenReturn(Arrays.asList("admin", "db1", "db2"));
|
||||||
|
when(mongoClientMock.getDB(eq("db1"))).thenReturn(db1mock);
|
||||||
|
when(mongoClientMock.getDB(eq("db2"))).thenReturn(db2mock);
|
||||||
|
when(db1mock.collectionExists(anyString())).thenReturn(true);
|
||||||
|
when(db2mock.collectionExists(anyString())).thenReturn(true);
|
||||||
|
when(db1mock.getCollectionNames()).thenReturn(Collections.singleton("collection-1"));
|
||||||
|
when(db2mock.getCollectionNames()).thenReturn(Collections.<String> emptySet());
|
||||||
|
when(db1mock.getCollectionFromString(eq("collection-1"))).thenReturn(collection1mock);
|
||||||
|
|
||||||
|
cleaner = new CleanMongoDB(mongoClientMock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void preservesSystemCollectionsCorrectly() throws Throwable {
|
||||||
|
|
||||||
|
cleaner.clean(Types.DATABASE);
|
||||||
|
|
||||||
|
cleaner.apply(baseStatementMock, descriptionMock).evaluate();
|
||||||
|
|
||||||
|
verify(mongoClientMock, times(1)).dropDatabase(eq("db1"));
|
||||||
|
verify(mongoClientMock, times(1)).dropDatabase(eq("db2"));
|
||||||
|
verify(mongoClientMock, never()).dropDatabase(eq("admin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removesCollectionsCorrectly() throws Throwable {
|
||||||
|
|
||||||
|
cleaner.clean(Types.COLLECTION);
|
||||||
|
|
||||||
|
cleaner.apply(baseStatementMock, descriptionMock).evaluate();
|
||||||
|
|
||||||
|
verify(mongoClientMock, never()).dropDatabase(eq("db1"));
|
||||||
|
verify(mongoClientMock, never()).dropDatabase(eq("db2"));
|
||||||
|
verify(mongoClientMock, never()).dropDatabase(eq("admin"));
|
||||||
|
|
||||||
|
verify(collection1mock, times(1)).drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removesIndexesCorrectly() throws Throwable {
|
||||||
|
|
||||||
|
cleaner.clean(Types.INDEX);
|
||||||
|
|
||||||
|
cleaner.apply(baseStatementMock, descriptionMock).evaluate();
|
||||||
|
|
||||||
|
verify(mongoClientMock, never()).dropDatabase(eq("db1"));
|
||||||
|
verify(mongoClientMock, never()).dropDatabase(eq("db2"));
|
||||||
|
verify(mongoClientMock, never()).dropDatabase(eq("admin"));
|
||||||
|
|
||||||
|
verify(collection1mock, times(1)).dropIndexes();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ Export-Template:
|
|||||||
org.springframework.data.mongodb.*;version="${project.version}"
|
org.springframework.data.mongodb.*;version="${project.version}"
|
||||||
Import-Template:
|
Import-Template:
|
||||||
com.google.common.base.*;version="[11.0.0,14.0.0)";resolution:=optional,
|
com.google.common.base.*;version="[11.0.0,14.0.0)";resolution:=optional,
|
||||||
com.mongodb.*;version="${mongo:[=.=.=,+1.0.0)}",
|
com.mongodb.*;version="${mongo.osgi:[=.=.=,+1.0.0)}",
|
||||||
com.mysema.query.*;version="[2.1.1, 3.0.0)";resolution:=optional,
|
com.mysema.query.*;version="[2.1.1, 3.0.0)";resolution:=optional,
|
||||||
javax.annotation.processing.*;version="0",
|
javax.annotation.processing.*;version="0",
|
||||||
javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional,
|
javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<xi:include href="introduction/introduction.xml"/>
|
<xi:include href="introduction/introduction.xml"/>
|
||||||
<xi:include href="introduction/requirements.xml"/>
|
<xi:include href="introduction/requirements.xml"/>
|
||||||
<xi:include href="introduction/getting-started.xml"/>
|
<xi:include href="introduction/getting-started.xml"/>
|
||||||
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RELEASE/src/docbkx/repositories.xml">
|
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.2.RELEASE/src/docbkx/repositories.xml">
|
||||||
<xi:fallback href="../../../spring-data-commons/src/docbkx/repositories.xml" />
|
<xi:fallback href="../../../spring-data-commons/src/docbkx/repositories.xml" />
|
||||||
</xi:include>
|
</xi:include>
|
||||||
</part>
|
</part>
|
||||||
@@ -88,10 +88,10 @@
|
|||||||
<part id="appendix">
|
<part id="appendix">
|
||||||
<title>Appendix</title>
|
<title>Appendix</title>
|
||||||
|
|
||||||
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RELEASE/src/docbkx/repository-namespace-reference.xml">
|
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.2.RELEASE/src/docbkx/repository-namespace-reference.xml">
|
||||||
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-namespace-reference.xml" />
|
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-namespace-reference.xml" />
|
||||||
</xi:include>
|
</xi:include>
|
||||||
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RELEASE/src/docbkx/repository-query-keywords-reference.xml">
|
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.2.RELEASE/src/docbkx/repository-query-keywords-reference.xml">
|
||||||
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-query-keywords-reference.xml" />
|
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-query-keywords-reference.xml" />
|
||||||
</xi:include>
|
</xi:include>
|
||||||
</part>
|
</part>
|
||||||
|
|||||||
@@ -18,20 +18,19 @@
|
|||||||
<section id="get-started:help:community">
|
<section id="get-started:help:community">
|
||||||
<title>Community Forum</title>
|
<title>Community Forum</title>
|
||||||
|
|
||||||
<para>The Spring Data <ulink
|
<para>Spring Data on Stackoverflow <ulink
|
||||||
url="http://forum.spring.io/forum/spring-projects/data">forum
|
url="http://stackoverflow.com/questions/tagged/spring-data">Stackoverflow
|
||||||
</ulink> is a message board for all Spring Data (not just Document)
|
</ulink> is a tag for all Spring Data (not just Document) users to share
|
||||||
users to share information and help each other. Note that registration
|
information and help each other. Note that registration is needed
|
||||||
is needed <emphasis>only</emphasis> for posting.</para>
|
<emphasis>only</emphasis> for posting.</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="get-started:help:professional">
|
<section id="get-started:help:professional">
|
||||||
<title>Professional Support</title>
|
<title>Professional Support</title>
|
||||||
|
|
||||||
<para>Professional, from-the-source support, with guaranteed response
|
<para>Professional, from-the-source support, with guaranteed response
|
||||||
time, is available from <ulink
|
time, is available from <ulink url="http://gopivotal.com/">Pivotal
|
||||||
url="http://gopivotal.com/">Pivotal Sofware, Inc.</ulink>, the company
|
Sofware, Inc.</ulink>, the company behind Spring Data and Spring.</para>
|
||||||
behind Spring Data and Spring.</para>
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -44,22 +43,21 @@
|
|||||||
homepage</ulink>.</para>
|
homepage</ulink>.</para>
|
||||||
|
|
||||||
<para>You can help make Spring Data best serve the needs of the Spring
|
<para>You can help make Spring Data best serve the needs of the Spring
|
||||||
community by interacting with developers through the Spring Community
|
community by interacting with developers through the Community on <ulink
|
||||||
<ulink url="http://forum.spring.io/">forums</ulink>. To follow
|
url="http://stackoverflow.com/questions/tagged/spring-data">Stackoverflow</ulink>.
|
||||||
developer activity look for the mailing list information on the Spring
|
To follow developer activity look for the mailing list information on the
|
||||||
Data Mongo homepage.</para>
|
Spring Data Mongo homepage.</para>
|
||||||
|
|
||||||
<para>If you encounter a bug or want to suggest an improvement, please
|
<para>If you encounter a bug or want to suggest an improvement, please
|
||||||
create a ticket on the Spring Data issue <ulink
|
create a ticket on the Spring Data issue <ulink
|
||||||
url="https://jira.springframework.org/browse/DATAMONGO">tracker</ulink>.</para>
|
url="https://jira.spring.io/browse/DATAMONGO">tracker</ulink>.</para>
|
||||||
|
|
||||||
<para>To stay up to date with the latest news and announcements in the
|
<para>To stay up to date with the latest news and announcements in the
|
||||||
Spring eco system, subscribe to the Spring Community <ulink
|
Spring eco system, subscribe to the Spring Community <ulink
|
||||||
url="https://spring.io">Portal</ulink>.</para>
|
url="https://spring.io">Portal</ulink>.</para>
|
||||||
|
|
||||||
<para>Lastly, you can follow the SpringSource Data <ulink
|
<para>Lastly, you can follow the SpringSource Data <ulink
|
||||||
url="https://spring.io/blog">blog </ulink>or
|
url="https://spring.io/blog">blog </ulink>or the project team on Twitter
|
||||||
the project team on Twitter (<ulink
|
(<ulink url="http://twitter.com/SpringData">SpringData</ulink>).</para>
|
||||||
url="http://twitter.com/SpringData">SpringData</ulink>)</para>
|
|
||||||
</section>
|
</section>
|
||||||
</chapter>
|
</chapter>
|
||||||
|
|||||||
@@ -1,6 +1,130 @@
|
|||||||
Spring Data MongoDB Changelog
|
Spring Data MongoDB Changelog
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
Changes in version 1.5.4.RELEASE (2014-08-27)
|
||||||
|
---------------------------------------------
|
||||||
|
* DATAMONGO-1038 - Assert Mongo instances cleand up properly after test runs.
|
||||||
|
* DATAMONGO-1034 - Improve error message when trying to convert incompatible types.
|
||||||
|
* DATAMONGO-1033 - Release 1.5.4.
|
||||||
|
* DATAMONGO-1030 - Query methods retuning a single entity does not work with projecting types.
|
||||||
|
* DATAMONGO-1027 - Collection inherits complex index from embedded class/object.
|
||||||
|
* DATAMONGO-1025 - Duplicate index creation on embedded documents.
|
||||||
|
* DATAMONGO-1020 - LimitOperator should be a public class.
|
||||||
|
* DATAMONGO-1008 - IndexOperations fail, when "2dsphere" index is present.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in version 1.6.0.RC1 (2014-08-13)
|
||||||
|
-----------------------------------------
|
||||||
|
* DATAMONGO-1024 - Upgrade to Java driver 2.12.3.
|
||||||
|
* DATAMONGO-1021 - Release 1.6 RC1.
|
||||||
|
* DATAMONGO-1020 - LimitOperator should be a public class.
|
||||||
|
* DATAMONGO-1019 - Correct examples in reference documentation.
|
||||||
|
* DATAMONGO-1017 - Add support for custom implementations in CDI repositories.
|
||||||
|
* DATAMONGO-1016 - Remove deprecations in geospatial area.
|
||||||
|
* DATAMONGO-1015 - Move to Asciidoctor for reference documentation.
|
||||||
|
* DATAMONGO-1012 - Proxies for lazy DBRefs with field access should have their id values resolved eagerly.
|
||||||
|
* DATAMONGO-1009 - Adapt to new multi-store configuration detection.
|
||||||
|
* DATAMONGO-1008 - IndexOperations fail, when "2dsphere" index is present.
|
||||||
|
* DATAMONGO-1005 - Improve cycle-detection for DbRef's.
|
||||||
|
* DATAMONGO-1002 - Update.toString(…) might throw exception.
|
||||||
|
* DATAMONGO-1001 - Can't save/update lazy load object.
|
||||||
|
* DATAMONGO-999 - Multiple Mongo Instances always have the same MongoOption Reference - MongoOptionsFactoryBean has a static instance of MongoOptions.
|
||||||
|
* DATAMONGO-996 - Pagination broken after introduction of the support for top/first.
|
||||||
|
* DATAMONGO-995 - Parameter binding in String-based query does not bind all parameters.
|
||||||
|
* DATAMONGO-993 - The system variables $$CURRENT and $$ROOT not handled correctly.
|
||||||
|
* DATAMONGO-992 - Entity can't be deserialized if @TypeAlias is used.
|
||||||
|
* DATAMONGO-991 - Adapt to deprecation removals in Spring Data Commons.
|
||||||
|
* DATAMONGO-989 - MatchOperation should accept CriteriaDefinition.
|
||||||
|
* DATAMONGO-987 - Problem with lazy loading in @DBRef when getting data using MongoTemplate.
|
||||||
|
* DATAMONGO-974 - synthetic field target's name is returned instead of the alias name.
|
||||||
|
* DATAMONGO-973 - Add support for deriving full text queries.
|
||||||
|
* DATAMONGO-957 - Add support for query modifiers.
|
||||||
|
* DATAMONGO-420 - Extra quotes being added to @Query values and fields.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in version 1.5.2.RELEASE (2014-07-28)
|
||||||
|
---------------------------------------------
|
||||||
|
* DATAMONGO-1007 - Release 1.5.2.
|
||||||
|
* DATAMONGO-1002 - Update.toString(…) might throw exception.
|
||||||
|
* DATAMONGO-1001 - Can't save/update lazy load object.
|
||||||
|
* DATAMONGO-999 - Multiple Mongo Instances always have the same MongoOption Reference - MongoOptionsFactoryBean has a static instance of MongoOptions.
|
||||||
|
* DATAMONGO-995 - Parameter binding in String-based query does not bind all parameters.
|
||||||
|
* DATAMONGO-992 - Entity can't be deserialized if @TypeAlias is used.
|
||||||
|
* DATAMONGO-989 - MatchOperation should accept CriteriaDefinition.
|
||||||
|
* DATAMONGO-987 - Problem with lazy loading in @DBRef when getting data using MongoTemplate.
|
||||||
|
* DATAMONGO-983 - Remove links to forum.spring.io.
|
||||||
|
* DATAMONGO-982 - Assure compatibility with upcoming MongoDB driver versions.
|
||||||
|
* DATAMONGO-978 - deleteBy/removeBy repository methods don't set type information in Before/AfterDeleteEvent.
|
||||||
|
* DATAMONGO-972 - References are not handled properly in Querydsl integration.
|
||||||
|
* DATAMONGO-969 - String @id field is not mapped to ObjectId when using QueryDSL .id.in(Collection<String>).
|
||||||
|
* DATAMONGO-420 - Extra quotes being added to @Query values and fields.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in version 1.6.0.M1 (2014-07-10)
|
||||||
|
----------------------------------------
|
||||||
|
* DATAMONGO-983 - Remove links to forum.spring.io.
|
||||||
|
* DATAMONGO-982 - Assure compatibility with upcoming MongoDB driver versions.
|
||||||
|
* DATAMONGO-981 - Release 1.6 M1.
|
||||||
|
* DATAMONGO-980 - Use meta annotations from spring data commons for @Score.
|
||||||
|
* DATAMONGO-978 - deleteBy/removeBy repository methods don't set type information in Before/AfterDeleteEvent.
|
||||||
|
* DATAMONGO-977 - Adapt to Spring 4 upgrade.
|
||||||
|
* DATAMONGO-976 - Add support for reading $meta projection on textScore into document.
|
||||||
|
* DATAMONGO-975 - Add support for date/time operators in aggregation framework.
|
||||||
|
* DATAMONGO-973 - Add support for deriving full text queries.
|
||||||
|
* DATAMONGO-972 - References are not handled properly in Querydsl integration.
|
||||||
|
* DATAMONGO-970 - Id query cannot be created if object to remove is DBObject.
|
||||||
|
* DATAMONGO-969 - String @id field is not mapped to ObjectId when using QueryDSL .id.in(Collection<String>).
|
||||||
|
* DATAMONGO-968 - Add support for $meta projections and sorting for textScore metadata.
|
||||||
|
* DATAMONGO-963 - Compound index with expireAfterSeconds causes repeating error on mongodb server.
|
||||||
|
* DATAMONGO-962 - “Cycle found” with Spring Data Mongo 1.5.
|
||||||
|
* DATAMONGO-960 - Allow to pass options to the Aggregation Pipeline.
|
||||||
|
* DATAMONGO-958 - Move to FieldNamingStrategy SPI in Spring Data Commons.
|
||||||
|
* DATAMONGO-954 - Add support for System Variables in Aggregations.
|
||||||
|
* DATAMONGO-953 - Update object should have a proper equals/hashcode/toString.
|
||||||
|
* DATAMONGO-952 - @Query annotation does not work with only field restrictions.
|
||||||
|
* DATAMONGO-950 - Add support for limiting the query result in the query derivation mechanism.
|
||||||
|
* DATAMONGO-949 - CyclicPropertyReferenceException in versions 1.5.0 + for MongoDB.
|
||||||
|
* DATAMONGO-948 - Assertion error in MongoTemplate.getMappedSortObject.
|
||||||
|
* DATAMONGO-944 - Add support $currentDate to Update.
|
||||||
|
* DATAMONGO-938 - Exception when creating geo within Criteria using MapReduce.
|
||||||
|
* DATAMONGO-937 - Add support for creating text index.
|
||||||
|
* DATAMONGO-850 - Add support for text search using $text.
|
||||||
|
* DATAMONGO-745 - @Query($in) and Pageable in result Page total = 0.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in version 1.5.1.RELEASE (2014-06-30)
|
||||||
|
---------------------------------------------
|
||||||
|
* DATAMONGO-971 - Release 1.5.1.
|
||||||
|
* DATAMONGO-970 - Id query cannot be created if object to remove is DBObject.
|
||||||
|
* DATAMONGO-963 - Compound index with expireAfterSeconds causes repeating error on mongodb server.
|
||||||
|
* DATAMONGO-962 - “Cycle found” with Spring Data Mongo 1.5.
|
||||||
|
* DATAMONGO-953 - Update object should have a proper equals/hashcode/toString.
|
||||||
|
* DATAMONGO-952 - @Query annotation does not work with only field restrictions.
|
||||||
|
* DATAMONGO-949 - CyclicPropertyReferenceException in versions 1.5.0 + for MongoDB.
|
||||||
|
* DATAMONGO-948 - Assertion error in MongoTemplate.getMappedSortObject.
|
||||||
|
* DATAMONGO-938 - Exception when creating geo within Criteria using MapReduce.
|
||||||
|
* DATAMONGO-745 - @Query($in) and Pageable in result Page total = 0.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in version 1.4.3.RELEASE (2014-06-18)
|
||||||
|
---------------------------------------------
|
||||||
|
* DATAMONGO-955 - Release 1.4.3.
|
||||||
|
* DATAMONGO-953 - Update object should have a proper equals/hashcode/toString.
|
||||||
|
* DATAMONGO-952 - @Query annotation does not work with only field restrictions.
|
||||||
|
* DATAMONGO-948 - Assertion error in MongoTemplate.getMappedSortObject.
|
||||||
|
* DATAMONGO-938 - Exception when creating geo within Criteria using MapReduce.
|
||||||
|
* DATAMONGO-924 - Aggregation not working with as() method in project() pipeline operator.
|
||||||
|
* DATAMONGO-920 - Fix debug messages for delete events in AbstractMongoEventListener.
|
||||||
|
* DATAMONGO-917 - DefaultDbRefResolver throws NPE when bundled into an uberjar.
|
||||||
|
* DATAMONGO-914 - Improve resolving of LazyLoading proxies for classes that override equals/hashcode.
|
||||||
|
* DATAMONGO-913 - Can't query using lazy DBRef objects.
|
||||||
|
* DATAMONGO-912 - Aggregation#project followed by Aggregation#match with custom converter causes IllegalArgumentException.
|
||||||
|
* DATAMONGO-898 - MapReduce seems not to work when javascript not being escaped.
|
||||||
|
* DATAMONGO-847 - Allow usage of Criteria within Update.
|
||||||
|
* DATAMONGO-745 - @Query($in) and Pageable in result Page total = 0.
|
||||||
|
* DATAMONGO-647 - Using "OrderBy" in "query by method name" ignores the @Field annotation for field alias.
|
||||||
|
|
||||||
|
|
||||||
Changes in version 1.5.0.RELEASE (2014-05-20)
|
Changes in version 1.5.0.RELEASE (2014-05-20)
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
* DATAMONGO-936 - Release 1.5 GA.
|
* DATAMONGO-936 - Release 1.5 GA.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Spring Data MongoDB 1.5 GA
|
Spring Data MongoDB 1.5.4
|
||||||
Copyright (c) [2010-2014] Pivotal Software, Inc.
|
Copyright (c) [2010-2014] Pivotal Software, Inc.
|
||||||
|
|
||||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||||
|
|||||||
Reference in New Issue
Block a user