Compare commits
32 Commits
1.5.2.RELE
...
1.6.0.M1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d861fecdb8 | ||
|
|
f280e23095 | ||
|
|
ed0e1d92c0 | ||
|
|
d82fc22659 | ||
|
|
6616d6788c | ||
|
|
322a7cf033 | ||
|
|
0f487c10ba | ||
|
|
11417144bd | ||
|
|
dafc59b163 | ||
|
|
566f9a80c4 | ||
|
|
89a42c5648 | ||
|
|
83ffbb00e8 | ||
|
|
84913cecab | ||
|
|
998bb09a92 | ||
|
|
cd68a8db54 | ||
|
|
df8477d180 | ||
|
|
244fbae0ce | ||
|
|
19e08a52c0 | ||
|
|
6389b1bb73 | ||
|
|
cadcbf6106 | ||
|
|
118f007ca6 | ||
|
|
cbb32bd29d | ||
|
|
9858dcd740 | ||
|
|
1fb76d135b | ||
|
|
bb62c8b2f1 | ||
|
|
2cbe7bf885 | ||
|
|
6043f6b74d | ||
|
|
ef1366592a | ||
|
|
01cf9fb8f3 | ||
|
|
285c406d5d | ||
|
|
ad29e52a57 | ||
|
|
3cfe207c83 |
@@ -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 [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.
|
||||
* 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/).
|
||||
|
||||
@@ -139,7 +139,7 @@ public class MyService {
|
||||
|
||||
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.
|
||||
* 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.
|
||||
|
||||
46
pom.xml
46
pom.xml
@@ -2,10 +2,10 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.5.0.RELEASE</version>
|
||||
<version>1.6.0.M1</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Spring Data MongoDB</name>
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>1.4.0.RELEASE</version>
|
||||
<version>1.5.0.M1</version>
|
||||
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -29,10 +29,11 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>1.8.0.RELEASE</springdata.commons>
|
||||
<springdata.commons>1.9.0.M1</springdata.commons>
|
||||
<mongo>2.12.1</mongo>
|
||||
<mongo.osgi>2.12.1</mongo.osgi>
|
||||
</properties>
|
||||
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>ogierke</id>
|
||||
@@ -104,10 +105,35 @@
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
|
||||
<id>mongo-next</id>
|
||||
<properties>
|
||||
<mongo>2.12.0</mongo>
|
||||
<mongo>2.12.3-SNAPSHOT</mongo>
|
||||
</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>
|
||||
</profiles>
|
||||
|
||||
@@ -119,14 +145,14 @@
|
||||
<version>${mongo}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-libs-release</id>
|
||||
<url>http://repo.spring.io/libs-release</url>
|
||||
<id>spring-libs-milestone</id>
|
||||
<url>http://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-plugins-release</id>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.5.0.RELEASE</version>
|
||||
<version>1.6.0.M1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>1.5.0.RELEASE</version>
|
||||
<version>1.6.0.M1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -7,7 +7,7 @@ Import-Package:
|
||||
Export-Template:
|
||||
org.springframework.data.mongodb.crossstore.*;version="${project.version}"
|
||||
Import-Template:
|
||||
com.mongodb.*;version="${mongo:[=.=.=,+1.0.0)}",
|
||||
com.mongodb.*;version="${mongo.osgi:[=.=.=,+1.0.0)}",
|
||||
javax.persistence.*;version="${jpa:[=.=.=,+1.0.0)}",
|
||||
org.aspectj.*;version="${aspectj:[1.0.0, 2.0.0)}",
|
||||
org.bson.*;version="0",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.5.0.RELEASE</version>
|
||||
<version>1.6.0.M1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.5.0.RELEASE</version>
|
||||
<version>1.6.0.M1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ Bundle-ManifestVersion: 2
|
||||
Import-Package:
|
||||
sun.reflect;version="0";resolution:=optional
|
||||
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)}"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.5.0.RELEASE</version>
|
||||
<version>1.6.0.M1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.data.authentication.UserCredentials;
|
||||
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
|
||||
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
|
||||
@@ -35,11 +38,8 @@ import org.springframework.data.mongodb.core.convert.CustomConversions;
|
||||
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.mapping.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.FieldNamingStrategy;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.PropertyNameFieldNamingStrategy;
|
||||
import org.springframework.data.support.CachingIsNewStrategyFactory;
|
||||
import org.springframework.data.support.IsNewStrategyFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -52,10 +52,10 @@ import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.data.config.BeanComponentDefinitionBuilder;
|
||||
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
|
||||
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mongodb.core.convert.CustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
|
||||
import org.springframework.data.mongodb.core.mapping.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,6 +36,7 @@ import com.mongodb.MongoException;
|
||||
* @author Mark Pollack
|
||||
* @author Oliver Gierke
|
||||
* @author Komi Innocent
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class DefaultIndexOperations implements IndexOperations {
|
||||
|
||||
@@ -142,6 +143,13 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
|
||||
if ("2d".equals(value)) {
|
||||
indexFields.add(IndexField.geo(key));
|
||||
} else if ("text".equals(value)) {
|
||||
|
||||
DBObject weights = (DBObject) ix.get("weights");
|
||||
for (String fieldName : weights.keySet()) {
|
||||
indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString())));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
Double keyValue = new Double(value.toString());
|
||||
@@ -159,8 +167,8 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
boolean unique = ix.containsField("unique") ? (Boolean) ix.get("unique") : false;
|
||||
boolean dropDuplicates = ix.containsField("dropDups") ? (Boolean) ix.get("dropDups") : false;
|
||||
boolean sparse = ix.containsField("sparse") ? (Boolean) ix.get("sparse") : false;
|
||||
|
||||
indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse));
|
||||
String language = ix.containsField("default_language") ? (String) ix.get("default_language") : "";
|
||||
indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language));
|
||||
}
|
||||
|
||||
return indexInfoList;
|
||||
|
||||
@@ -23,11 +23,15 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
|
||||
import com.mongodb.MongoCursorNotFoundException;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.MongoException.CursorNotFound;
|
||||
import com.mongodb.MongoException.DuplicateKey;
|
||||
import com.mongodb.MongoException.Network;
|
||||
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
|
||||
@@ -47,21 +51,23 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
|
||||
// Check for well-known MongoException subclasses.
|
||||
|
||||
// All other MongoExceptions
|
||||
if (ex instanceof DuplicateKey) {
|
||||
if (ex instanceof DuplicateKey || ex instanceof DuplicateKeyException) {
|
||||
return new DuplicateKeyException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof Network) {
|
||||
if (ex instanceof Network || ex instanceof MongoSocketException) {
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof CursorNotFound) {
|
||||
if (ex instanceof CursorNotFound || ex instanceof MongoCursorNotFoundException) {
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// Driver 2.12 throws this to indicate connection problems. String comparison to avoid hard dependency
|
||||
if (ex.getClass().getName().equals("com.mongodb.MongoServerSelectionException")) {
|
||||
if (ex instanceof MongoServerSelectionException) {
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof MongoTimeoutException) {
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
@@ -69,6 +75,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// All other MongoExceptions
|
||||
if (ex instanceof MongoException) {
|
||||
|
||||
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
|
||||
* {@link Entry#getKey()} and the {@link Id}s property value as its {@link Entry#getValue()}.
|
||||
* Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s
|
||||
* property value as its {@link Entry#getValue()}.
|
||||
*
|
||||
* @param object
|
||||
* @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'.");
|
||||
|
||||
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);
|
||||
MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty();
|
||||
|
||||
@@ -1090,7 +1095,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
|
||||
Object idValue = BeanWrapper.create(object, mongoConverter.getConversionService())
|
||||
.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) {
|
||||
|
||||
Map.Entry<MongoPersistentProperty, Object> id = extractIdPropertyAndValue(object);
|
||||
return new Query(where(id.getKey().getFieldName()).is(id.getValue()));
|
||||
Entry<String, Object> id = extractIdPropertyAndValue(object);
|
||||
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.");
|
||||
|
||||
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());
|
||||
ids.add(firstEntry.getValue());
|
||||
@@ -1125,7 +1130,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
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) {
|
||||
@@ -1413,17 +1418,32 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
CommandResult commandResult = executeCommand(command);
|
||||
handleCommandError(commandResult, command);
|
||||
|
||||
// map results
|
||||
return new AggregationResults<O>(returnPotentiallyMappedResults(outputType, commandResult), commandResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the potentially mapped results of the given {@commandResult} contained some.
|
||||
*
|
||||
* @param outputType
|
||||
* @param commandResult
|
||||
* @return
|
||||
*/
|
||||
private <O> List<O> returnPotentiallyMappedResults(Class<O> outputType, CommandResult commandResult) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterable<DBObject> resultSet = (Iterable<DBObject>) commandResult.get("result");
|
||||
List<O> mappedResults = new ArrayList<O>();
|
||||
if (resultSet == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
DbObjectCallback<O> callback = new UnwrapAndReadDbObjectCallback<O>(mongoConverter, outputType);
|
||||
|
||||
List<O> mappedResults = new ArrayList<O>();
|
||||
for (DBObject dbObject : resultSet) {
|
||||
mappedResults.add(callback.doWith(dbObject));
|
||||
}
|
||||
|
||||
return new AggregationResults<O>(mappedResults, commandResult);
|
||||
return mappedResults;
|
||||
}
|
||||
|
||||
protected String replaceWithResourceIfNecessary(String function) {
|
||||
@@ -1455,13 +1475,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
"Can not use skip or field specification with map reduce operations");
|
||||
}
|
||||
if (query.getQueryObject() != null) {
|
||||
copyMapReduceOptions.put("query", query.getQueryObject());
|
||||
copyMapReduceOptions.put("query", queryMapper.getMappedObject(query.getQueryObject(), null));
|
||||
}
|
||||
if (query.getLimit() > 0) {
|
||||
copyMapReduceOptions.put("limit", query.getLimit());
|
||||
}
|
||||
if (query.getSortObject() != null) {
|
||||
copyMapReduceOptions.put("sort", query.getSortObject());
|
||||
copyMapReduceOptions.put("sort", queryMapper.getMappedObject(query.getSortObject(), null));
|
||||
}
|
||||
}
|
||||
return copyMapReduceOptions;
|
||||
@@ -2179,7 +2199,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
cursorToUse = cursorToUse.limit(query.getLimit());
|
||||
}
|
||||
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())) {
|
||||
cursorToUse = cursorToUse.hint(query.getHint());
|
||||
|
||||
@@ -44,9 +44,23 @@ import com.mongodb.DBObject;
|
||||
*/
|
||||
public class Aggregation {
|
||||
|
||||
public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext();
|
||||
/**
|
||||
* References the root document, i.e. the top-level document, currently being processed in the aggregation pipeline
|
||||
* stage.
|
||||
*/
|
||||
public static final String ROOT = SystemVariable.ROOT.toString();
|
||||
|
||||
private final List<AggregationOperation> operations;
|
||||
/**
|
||||
* References the start of the field path being processed in the aggregation pipeline stage. Unless documented
|
||||
* otherwise, all stages start with CURRENT the same as ROOT.
|
||||
*/
|
||||
public static final String CURRENT = SystemVariable.CURRENT.toString();
|
||||
|
||||
public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext();
|
||||
public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build();
|
||||
|
||||
protected final List<AggregationOperation> operations;
|
||||
private final AggregationOptions options;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
|
||||
@@ -66,6 +80,20 @@ public class Aggregation {
|
||||
return new Aggregation(operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@link Aggregation} with the given {@link AggregationOptions} set. Note that options are
|
||||
* supported in MongoDB version 2.6+.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public Aggregation withOptions(AggregationOptions options) {
|
||||
|
||||
Assert.notNull(options, "AggregationOptions must not be null.");
|
||||
return new Aggregation(this.operations, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypedAggregation} for the given type and {@link AggregationOperation}s.
|
||||
*
|
||||
@@ -92,11 +120,43 @@ public class Aggregation {
|
||||
* @param aggregationOperations must not be {@literal null} or empty.
|
||||
*/
|
||||
protected Aggregation(AggregationOperation... aggregationOperations) {
|
||||
this(asAggregationList(aggregationOperations));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aggregationOperations must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
protected static List<AggregationOperation> asAggregationList(AggregationOperation... aggregationOperations) {
|
||||
|
||||
Assert.notEmpty(aggregationOperations, "AggregationOperations must not be null or empty!");
|
||||
|
||||
return Arrays.asList(aggregationOperations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @param aggregationOperations must not be {@literal null} or empty.
|
||||
*/
|
||||
protected Aggregation(List<AggregationOperation> aggregationOperations) {
|
||||
this(aggregationOperations, DEFAULT_OPTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @param aggregationOperations must not be {@literal null} or empty.
|
||||
* @param options must not be {@literal null} or empty.
|
||||
*/
|
||||
protected Aggregation(List<AggregationOperation> aggregationOperations, AggregationOptions options) {
|
||||
|
||||
Assert.notNull(aggregationOperations, "AggregationOperations must not be null!");
|
||||
Assert.isTrue(aggregationOperations.length > 0, "At least one AggregationOperation has to be provided");
|
||||
Assert.isTrue(aggregationOperations.size() > 0, "At least one AggregationOperation has to be provided");
|
||||
Assert.notNull(options, "AggregationOptions must not be null!");
|
||||
|
||||
this.operations = Arrays.asList(aggregationOperations);
|
||||
this.operations = aggregationOperations;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,6 +291,16 @@ public class Aggregation {
|
||||
return Fields.from(field(name, target));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link AggregationOptions.Builder}.
|
||||
*
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public static AggregationOptions.Builder newAggregationOptions() {
|
||||
return new AggregationOptions.Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this {@link Aggregation} specification to a {@link DBObject}.
|
||||
*
|
||||
@@ -255,6 +325,8 @@ public class Aggregation {
|
||||
DBObject command = new BasicDBObject("aggregate", inputCollectionName);
|
||||
command.put("pipeline", operationDocuments);
|
||||
|
||||
command = options.applyAndReturnPotentiallyChangedCommand(command);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
@@ -302,4 +374,51 @@ public class Aggregation {
|
||||
return new FieldReference(new ExposedField(new AggregationField(name), true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the system variables available in MongoDB aggregation framework pipeline expressions.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation-variables
|
||||
*/
|
||||
enum SystemVariable {
|
||||
|
||||
ROOT, CURRENT;
|
||||
|
||||
private static final String PREFIX = "$$";
|
||||
|
||||
/**
|
||||
* Return {@literal true} if the given {@code fieldRef} denotes a well-known system variable, {@literal false}
|
||||
* otherwise.
|
||||
*
|
||||
* @param fieldRef may be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static boolean isReferingToSystemVariable(String fieldRef) {
|
||||
|
||||
if (fieldRef == null || !fieldRef.startsWith(PREFIX) || fieldRef.length() <= 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int indexOfFirstDot = fieldRef.indexOf('.');
|
||||
String candidate = fieldRef.substring(2, indexOfFirstDot == -1 ? fieldRef.length() : indexOfFirstDot);
|
||||
|
||||
for (SystemVariable value : values()) {
|
||||
if (value.name().equals(candidate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Enum#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return PREFIX.concat(name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Holds a set of configurable aggregation options that can be used within an aggregation pipeline. A list of support
|
||||
* aggregation options can be found in the MongoDB reference documentation
|
||||
* http://docs.mongodb.org/manual/reference/command/aggregate/#aggregate
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @see Aggregation#withOptions(AggregationOptions)
|
||||
* @see TypedAggregation#withOptions(AggregationOptions)
|
||||
* @since 1.6
|
||||
*/
|
||||
public class AggregationOptions {
|
||||
|
||||
private static final String CURSOR = "cursor";
|
||||
private static final String EXPLAIN = "explain";
|
||||
private static final String ALLOW_DISK_USE = "allowDiskUse";
|
||||
|
||||
private final boolean allowDiskUse;
|
||||
private final boolean explain;
|
||||
private final DBObject cursor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AggregationOptions}.
|
||||
*
|
||||
* @param allowDiskUse whether to off-load intensive sort-operations to disk.
|
||||
* @param explain whether to get the execution plan for the aggregation instead of the actual results.
|
||||
* @param cursor can be {@literal null}, used to pass additional options to the aggregation.
|
||||
*/
|
||||
public AggregationOptions(boolean allowDiskUse, boolean explain, DBObject cursor) {
|
||||
|
||||
this.allowDiskUse = allowDiskUse;
|
||||
this.explain = explain;
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables writing to temporary files. When set to true, aggregation stages can write data to the _tmp subdirectory in
|
||||
* the dbPath directory.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isAllowDiskUse() {
|
||||
return allowDiskUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies to return the information on the processing of the pipeline.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isExplain() {
|
||||
return explain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a document that contains options that control the creation of the cursor object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public DBObject getCursor() {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new potentially adjusted copy for the given {@code aggregationCommandObject} with the configuration
|
||||
* applied.
|
||||
*
|
||||
* @param command the aggregation command.
|
||||
* @return
|
||||
*/
|
||||
DBObject applyAndReturnPotentiallyChangedCommand(DBObject command) {
|
||||
|
||||
DBObject result = new BasicDBObject(command.toMap());
|
||||
|
||||
if (allowDiskUse && !result.containsField(ALLOW_DISK_USE)) {
|
||||
result.put(ALLOW_DISK_USE, allowDiskUse);
|
||||
}
|
||||
|
||||
if (explain && !result.containsField(EXPLAIN)) {
|
||||
result.put(EXPLAIN, explain);
|
||||
}
|
||||
|
||||
if (cursor != null && !result.containsField(CURSOR)) {
|
||||
result.put("cursor", cursor);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link DBObject} representation of this {@link AggregationOptions}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public DBObject toDbObject() {
|
||||
|
||||
DBObject dbo = new BasicDBObject();
|
||||
dbo.put(ALLOW_DISK_USE, allowDiskUse);
|
||||
dbo.put(EXPLAIN, explain);
|
||||
dbo.put(CURSOR, cursor);
|
||||
|
||||
return dbo;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return toDbObject().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Builder for {@link AggregationOptions}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private boolean allowDiskUse;
|
||||
private boolean explain;
|
||||
private DBObject cursor;
|
||||
|
||||
/**
|
||||
* Defines whether to off-load intensive sort-operations to disk.
|
||||
*
|
||||
* @param allowDiskUse
|
||||
* @return
|
||||
*/
|
||||
public Builder allowDiskUse(boolean allowDiskUse) {
|
||||
|
||||
this.allowDiskUse = allowDiskUse;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines whether to get the execution plan for the aggregation instead of the actual results.
|
||||
*
|
||||
* @param explain
|
||||
* @return
|
||||
*/
|
||||
public Builder explain(boolean explain) {
|
||||
|
||||
this.explain = explain;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional options to the aggregation.
|
||||
*
|
||||
* @param cursor
|
||||
* @return
|
||||
*/
|
||||
public Builder cursor(DBObject cursor) {
|
||||
|
||||
this.cursor = cursor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link AggregationOptions} instance with the given configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public AggregationOptions build() {
|
||||
return new AggregationOptions(allowDiskUse, explain, cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ import com.mongodb.DBObject;
|
||||
*
|
||||
* @author Tobias Trelle
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @param <T> The class in which the results are mapped onto.
|
||||
* @since 1.3
|
||||
*/
|
||||
@@ -90,6 +91,16 @@ public class AggregationResults<T> implements Iterable<T> {
|
||||
return serverUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw result that was returned by the server.
|
||||
*
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public DBObject getRawResults() {
|
||||
return rawResults;
|
||||
}
|
||||
|
||||
private String parseServerUsed() {
|
||||
|
||||
Object object = rawResults.get("serverUsed");
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.util.StringUtils;
|
||||
* Value object to capture a list of {@link Field} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @since 1.3
|
||||
*/
|
||||
public final class Fields implements Iterable<Field> {
|
||||
@@ -186,7 +187,7 @@ public final class Fields implements Iterable<Field> {
|
||||
private final String target;
|
||||
|
||||
/**
|
||||
* Creates an aggregation fieldwith the given name. As no target is set explicitly, the name will be used as target
|
||||
* Creates an aggregation field with the given name. As no target is set explicitly, the name will be used as target
|
||||
* as well.
|
||||
*
|
||||
* @param key
|
||||
@@ -217,6 +218,10 @@ public final class Fields implements Iterable<Field> {
|
||||
return source;
|
||||
}
|
||||
|
||||
if (Aggregation.SystemVariable.isReferingToSystemVariable(source)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
int dollarIndex = source.lastIndexOf('$');
|
||||
return dollarIndex == -1 ? source : source.substring(dollarIndex + 1);
|
||||
}
|
||||
|
||||
@@ -364,7 +364,16 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
public Object getValue(AggregationOperationContext context) {
|
||||
return reference == null ? value : context.getReference(reference).toString();
|
||||
|
||||
if (reference == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (Aggregation.SystemVariable.isReferingToSystemVariable(reference)) {
|
||||
return reference;
|
||||
}
|
||||
|
||||
return context.getReference(reference).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedFi
|
||||
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
@@ -235,26 +234,53 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link ProjectionOperationBuilder} that is used for SpEL expression based projections.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public static class ExpressionProjectionOperationBuilder extends AbstractProjectionOperationBuilder {
|
||||
public static class ExpressionProjectionOperationBuilder extends ProjectionOperationBuilder {
|
||||
|
||||
private final Object[] params;
|
||||
private final String expression;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExpressionProjectionOperationBuilder} for the given value, {@link ProjectionOperation} and
|
||||
* parameters.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param operation must not be {@literal null}.
|
||||
* @param parameters
|
||||
*/
|
||||
public ExpressionProjectionOperationBuilder(Object value, ProjectionOperation operation, Object[] parameters) {
|
||||
public ExpressionProjectionOperationBuilder(String expression, ProjectionOperation operation, Object[] parameters) {
|
||||
|
||||
super(value, operation);
|
||||
super(expression, operation, null);
|
||||
this.expression = expression;
|
||||
this.params = parameters.clone();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder#project(java.lang.String, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ProjectionOperationBuilder project(String operation, final Object... values) {
|
||||
|
||||
OperationProjection operationProjection = new OperationProjection(Fields.field(value.toString()), operation,
|
||||
values) {
|
||||
@Override
|
||||
protected List<Object> getOperationArguments(AggregationOperationContext context) {
|
||||
|
||||
List<Object> result = new ArrayList<Object>(values.length + 1);
|
||||
result.add(ExpressionProjection.toMongoExpression(context,
|
||||
ExpressionProjectionOperationBuilder.this.expression, ExpressionProjectionOperationBuilder.this.params));
|
||||
result.addAll(Arrays.asList(values));
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
return new ProjectionOperationBuilder(value, this.operation.and(operationProjection), operationProjection);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#as(java.lang.String)
|
||||
@@ -303,7 +329,11 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
return new BasicDBObject(getExposedField().getName(), TRANSFORMER.transform(expression, context, params));
|
||||
return new BasicDBObject(getExposedField().getName(), toMongoExpression(context, expression, params));
|
||||
}
|
||||
|
||||
protected static Object toMongoExpression(AggregationOperationContext context, String expression, Object[] params) {
|
||||
return TRANSFORMER.transform(expression, context, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,7 +350,6 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
private static final String FIELD_REFERENCE_NOT_NULL = "Field reference must not be null!";
|
||||
|
||||
private final String name;
|
||||
private final ProjectionOperation operation;
|
||||
private final OperationProjection previousProjection;
|
||||
|
||||
/**
|
||||
@@ -335,7 +364,23 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
super(name, operation);
|
||||
|
||||
this.name = name;
|
||||
this.operation = operation;
|
||||
this.previousProjection = previousProjection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ProjectionOperationBuilder} for the field with the given value on top of the given
|
||||
* {@link ProjectionOperation}.
|
||||
*
|
||||
* @param value
|
||||
* @param operation
|
||||
* @param previousProjection
|
||||
*/
|
||||
protected ProjectionOperationBuilder(Object value, ProjectionOperation operation,
|
||||
OperationProjection previousProjection) {
|
||||
|
||||
super(value, operation);
|
||||
|
||||
this.name = null;
|
||||
this.previousProjection = previousProjection;
|
||||
}
|
||||
|
||||
@@ -521,8 +566,9 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder project(String operation, Object... values) {
|
||||
OperationProjection projectionOperation = new OperationProjection(Fields.field(name), operation, values);
|
||||
return new ProjectionOperationBuilder(name, this.operation.and(projectionOperation), projectionOperation);
|
||||
OperationProjection operationProjection = new OperationProjection(Fields.field(value.toString()), operation,
|
||||
values);
|
||||
return new ProjectionOperationBuilder(value, this.operation.and(operationProjection), operationProjection);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -627,6 +673,10 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
// implicit reference or explicit include?
|
||||
if (value == null || Boolean.TRUE.equals(value)) {
|
||||
|
||||
if (Aggregation.SystemVariable.isReferingToSystemVariable(field.getTarget())) {
|
||||
return field.getTarget();
|
||||
}
|
||||
|
||||
// check whether referenced field exists in the context
|
||||
return context.getReference(field).getReferenceValue();
|
||||
|
||||
@@ -649,7 +699,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
/**
|
||||
* Creates a new {@link OperationProjection} for the given field.
|
||||
*
|
||||
* @param name the name of the field to add the operation projection for, must not be {@literal null} or empty.
|
||||
* @param field the name of the field to add the operation projection for, must not be {@literal null} or empty.
|
||||
* @param operation the actual operation key, must not be {@literal null} or empty.
|
||||
* @param values the values to pass into the operation, must not be {@literal null}.
|
||||
*/
|
||||
@@ -672,18 +722,15 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
@Override
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
|
||||
BasicDBList values = new BasicDBList();
|
||||
values.addAll(buildReferences(context));
|
||||
DBObject inner = new BasicDBObject("$" + operation, getOperationArguments(context));
|
||||
|
||||
DBObject inner = new BasicDBObject("$" + operation, values);
|
||||
|
||||
return new BasicDBObject(this.field.getName(), inner);
|
||||
return new BasicDBObject(getField().getName(), inner);
|
||||
}
|
||||
|
||||
private List<Object> buildReferences(AggregationOperationContext context) {
|
||||
protected List<Object> getOperationArguments(AggregationOperationContext context) {
|
||||
|
||||
List<Object> result = new ArrayList<Object>(values.size());
|
||||
result.add(context.getReference(field.getTarget()).toString());
|
||||
result.add(context.getReference(getField().getName()).toString());
|
||||
|
||||
for (Object element : values) {
|
||||
result.add(element instanceof Field ? context.getReference((Field) element).toString() : element);
|
||||
@@ -692,6 +739,15 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field that holds the {@link OperationProjection}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected Field getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of this {@link OperationProjection} with the given alias.
|
||||
*
|
||||
@@ -699,7 +755,27 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
* @return
|
||||
*/
|
||||
public OperationProjection withAlias(String alias) {
|
||||
return new OperationProjection(Fields.field(alias, this.field.getName()), operation, values.toArray());
|
||||
|
||||
final Field aliasedField = Fields.field(alias, this.field.getName());
|
||||
return new OperationProjection(aliasedField, operation, values.toArray()) {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.OperationProjection#getField()
|
||||
*/
|
||||
@Override
|
||||
protected Field getField() {
|
||||
return aliasedField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Object> getOperationArguments(AggregationOperationContext context) {
|
||||
|
||||
// We have to make sure that we use the arguments from the "previous" OperationProjection that we replace
|
||||
// with this new instance.
|
||||
|
||||
return OperationProjection.this.getOperationArguments(context);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,6 +807,96 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return new BasicDBObject(name, nestedObject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the minute from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractMinute() {
|
||||
return project("minute");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the hour from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractHour() {
|
||||
return project("hour");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the second from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractSecond() {
|
||||
return project("second");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the millisecond from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractMillisecond() {
|
||||
return project("millisecond");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the year from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractYear() {
|
||||
return project("year");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the month from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractMonth() {
|
||||
return project("month");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the week from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractWeek() {
|
||||
return project("week");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the dayOfYear from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractDayOfYear() {
|
||||
return project("dayOfYear");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the dayOfMonth from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractDayOfMonth() {
|
||||
return project("dayOfMonth");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the dayOfWeek from a date expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperationBuilder extractDayOfWeek() {
|
||||
return project("dayOfWeek");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -771,5 +937,4 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
*/
|
||||
public abstract DBObject toDBObject(AggregationOperationContext context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -30,11 +32,34 @@ public class TypedAggregation<I> extends Aggregation {
|
||||
/**
|
||||
* Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @param inputType must not be {@literal null}.
|
||||
* @param operations must not be {@literal null} or empty.
|
||||
*/
|
||||
public TypedAggregation(Class<I> inputType, AggregationOperation... operations) {
|
||||
this(inputType, asAggregationList(operations));
|
||||
}
|
||||
|
||||
super(operations);
|
||||
/**
|
||||
* Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @param inputType must not be {@literal null}.
|
||||
* @param operations must not be {@literal null} or empty.
|
||||
*/
|
||||
public TypedAggregation(Class<I> inputType, List<AggregationOperation> operations) {
|
||||
this(inputType, operations, DEFAULT_OPTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s and the given
|
||||
* {@link AggregationOptions}.
|
||||
*
|
||||
* @param inputType must not be {@literal null}.
|
||||
* @param operations must not be {@literal null} or empty.
|
||||
* @param options must not be {@literal null}.
|
||||
*/
|
||||
public TypedAggregation(Class<I> inputType, List<AggregationOperation> operations, AggregationOptions options) {
|
||||
|
||||
super(operations, options);
|
||||
|
||||
Assert.notNull(inputType, "Input type must not be null!");
|
||||
this.inputType = inputType;
|
||||
@@ -48,4 +73,14 @@ public class TypedAggregation<I> extends Aggregation {
|
||||
public Class<I> getInputType() {
|
||||
return inputType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.Aggregation#withOptions(org.springframework.data.mongodb.core.aggregation.AggregationOptions)
|
||||
*/
|
||||
public TypedAggregation<I> withOptions(AggregationOptions options) {
|
||||
|
||||
Assert.notNull(options, "AggregationOptions must not be null.");
|
||||
return new TypedAggregation<I>(inputType, operations, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ import java.lang.reflect.Method;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.objenesis.Objenesis;
|
||||
import org.objenesis.ObjenesisStd;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.Factory;
|
||||
@@ -39,8 +36,8 @@ import org.springframework.data.mongodb.LazyLoadingException;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -57,10 +54,9 @@ import com.mongodb.DBRef;
|
||||
*/
|
||||
public class DefaultDbRefResolver implements DbRefResolver {
|
||||
|
||||
private static final boolean OBJENESIS_PRESENT = ClassUtils.isPresent("org.objenesis.Objenesis", null);
|
||||
|
||||
private final MongoDbFactory mongoDbFactory;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
private final ObjenesisStd objenesis;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultDbRefResolver} with the given {@link MongoDbFactory}.
|
||||
@@ -73,6 +69,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
|
||||
this.mongoDbFactory = mongoDbFactory;
|
||||
this.exceptionTranslator = mongoDbFactory.getExceptionTranslator();
|
||||
this.objenesis = new ObjenesisStd(true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -117,32 +114,44 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
*/
|
||||
private Object createLazyLoadingProxy(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback) {
|
||||
|
||||
ProxyFactory proxyFactory = new ProxyFactory();
|
||||
Class<?> propertyType = property.getType();
|
||||
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback);
|
||||
|
||||
if (!propertyType.isInterface()) {
|
||||
|
||||
Factory factory = (Factory) objenesis.newInstance(getEnhancedTypeFor(propertyType));
|
||||
factory.setCallbacks(new Callback[] { interceptor });
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
ProxyFactory proxyFactory = new ProxyFactory();
|
||||
|
||||
for (Class<?> type : propertyType.getInterfaces()) {
|
||||
proxyFactory.addInterface(type);
|
||||
}
|
||||
|
||||
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback);
|
||||
|
||||
proxyFactory.addInterface(LazyLoadingProxy.class);
|
||||
proxyFactory.addInterface(propertyType);
|
||||
proxyFactory.addAdvice(interceptor);
|
||||
|
||||
if (propertyType.isInterface()) {
|
||||
proxyFactory.addInterface(propertyType);
|
||||
proxyFactory.addAdvice(interceptor);
|
||||
return proxyFactory.getProxy();
|
||||
}
|
||||
return proxyFactory.getProxy();
|
||||
}
|
||||
|
||||
proxyFactory.setProxyTargetClass(true);
|
||||
proxyFactory.setTargetClass(propertyType);
|
||||
/**
|
||||
* Returns the CGLib enhanced type for the given source type.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
private Class<?> getEnhancedTypeFor(Class<?> type) {
|
||||
|
||||
if (!OBJENESIS_PRESENT) {
|
||||
proxyFactory.addAdvice(interceptor);
|
||||
return proxyFactory.getProxy();
|
||||
}
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(type);
|
||||
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
|
||||
enhancer.setInterfaces(new Class[] { LazyLoadingProxy.class });
|
||||
|
||||
return ObjenesisProxyEnhancer.enhanceAndGet(proxyFactory, propertyType, interceptor);
|
||||
return enhancer.createClass();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,109 +380,4 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static class to accommodate optional dependency on Objenesis.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @since 1.4
|
||||
*/
|
||||
private static class ObjenesisProxyEnhancer {
|
||||
|
||||
private static final boolean IS_SPRING_4_OR_BETTER = ClassUtils.isPresent(
|
||||
"org.springframework.core.DefaultParameterNameDiscoverer", null);
|
||||
|
||||
private static final InstanceCreatorStrategy INSTANCE_CREATOR;
|
||||
|
||||
static {
|
||||
|
||||
if (IS_SPRING_4_OR_BETTER) {
|
||||
INSTANCE_CREATOR = new Spring4ObjenesisInstanceCreatorStrategy();
|
||||
} else {
|
||||
INSTANCE_CREATOR = new DefaultObjenesisInstanceCreatorStrategy();
|
||||
}
|
||||
}
|
||||
|
||||
public static Object enhanceAndGet(ProxyFactory proxyFactory, Class<?> type,
|
||||
org.springframework.cglib.proxy.MethodInterceptor interceptor) {
|
||||
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(type);
|
||||
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
|
||||
enhancer.setInterfaces(new Class[] { LazyLoadingProxy.class });
|
||||
|
||||
Factory factory = (Factory) INSTANCE_CREATOR.newInstance(enhancer.createClass());
|
||||
factory.setCallbacks(new Callback[] { interceptor });
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy for constructing new instances of a given {@link Class}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
interface InstanceCreatorStrategy {
|
||||
Object newInstance(Class<?> clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link InstanceCreatorStrategy} that uses Objenesis from the classpath.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
private static class DefaultObjenesisInstanceCreatorStrategy implements InstanceCreatorStrategy {
|
||||
|
||||
private static final Objenesis OBJENESIS = new ObjenesisStd(true);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.ObjenesisProxyEnhancer.InstanceCreatorStrategy#newInstance(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Object newInstance(Class<?> clazz) {
|
||||
return OBJENESIS.newInstance(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link InstanceCreatorStrategy} that uses a repackaged version of Objenesis from Spring 4.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
private static class Spring4ObjenesisInstanceCreatorStrategy implements InstanceCreatorStrategy {
|
||||
|
||||
private static final String SPRING4_OBJENESIS_CLASS_NAME = "org.springframework.objenesis.ObjenesisStd";
|
||||
private static final Object OBJENESIS;
|
||||
private static final Method NEW_INSTANCE_METHOD;
|
||||
|
||||
static {
|
||||
|
||||
try {
|
||||
Class<?> objenesisClass = ClassUtils.forName(SPRING4_OBJENESIS_CLASS_NAME,
|
||||
ObjenesisProxyEnhancer.class.getClassLoader());
|
||||
|
||||
OBJENESIS = BeanUtils.instantiateClass(objenesisClass.getConstructor(boolean.class), true);
|
||||
NEW_INSTANCE_METHOD = objenesisClass.getMethod("newInstance", Class.class);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not setup Objenesis infrastructure with Spring 4 ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.ObjenesisProxyEnhancer.InstanceCreatorStrategy#newInstance(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Object newInstance(Class<?> clazz) {
|
||||
|
||||
try {
|
||||
return NEW_INSTANCE_METHOD.invoke(OBJENESIS, clazz);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not created instance for " + clazz, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
||||
public void doWithPersistentProperty(MongoPersistentProperty prop) {
|
||||
|
||||
if (prop.equals(idProperty)) {
|
||||
if (prop.equals(idProperty) || !prop.isWritable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,8 +107,11 @@ public @interface CompoundIndex {
|
||||
/**
|
||||
* 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/
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
int expireAfterSeconds() default -1;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -24,22 +24,33 @@ import org.springframework.util.ObjectUtils;
|
||||
* Value object for an index field.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public final class IndexField {
|
||||
|
||||
enum Type {
|
||||
GEO, TEXT, DEFAULT;
|
||||
}
|
||||
|
||||
private final String key;
|
||||
private final Direction direction;
|
||||
private final boolean isGeo;
|
||||
private final Type type;
|
||||
private final Float weight;
|
||||
|
||||
private IndexField(String key, Direction direction, boolean isGeo) {
|
||||
private IndexField(String key, Direction direction, Type type) {
|
||||
this(key, direction, type, Float.NaN);
|
||||
}
|
||||
|
||||
private IndexField(String key, Direction direction, Type type, Float weight) {
|
||||
|
||||
Assert.hasText(key);
|
||||
Assert.isTrue(direction != null ^ isGeo);
|
||||
Assert.isTrue(direction != null ^ (Type.GEO.equals(type) || Type.TEXT.equals(type)));
|
||||
|
||||
this.key = key;
|
||||
this.direction = direction;
|
||||
this.isGeo = isGeo;
|
||||
this.type = type == null ? Type.DEFAULT : type;
|
||||
this.weight = weight == null ? Float.NaN : weight;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,12 +64,12 @@ public final class IndexField {
|
||||
@Deprecated
|
||||
public static IndexField create(String key, Order order) {
|
||||
Assert.notNull(order);
|
||||
return new IndexField(key, order.toDirection(), false);
|
||||
return new IndexField(key, order.toDirection(), Type.DEFAULT);
|
||||
}
|
||||
|
||||
public static IndexField create(String key, Direction order) {
|
||||
Assert.notNull(order);
|
||||
return new IndexField(key, order, false);
|
||||
return new IndexField(key, order, Type.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +79,16 @@ public final class IndexField {
|
||||
* @return
|
||||
*/
|
||||
public static IndexField geo(String key) {
|
||||
return new IndexField(key, null, true);
|
||||
return new IndexField(key, null, Type.GEO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a text {@link IndexField} for the given key.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
public static IndexField text(String key, Float weight) {
|
||||
return new IndexField(key, null, Type.TEXT, weight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,10 +121,20 @@ public final class IndexField {
|
||||
/**
|
||||
* Returns whether the {@link IndexField} is a geo index field.
|
||||
*
|
||||
* @return the isGeo
|
||||
* @return true if type is {@link Type#GEO}.
|
||||
*/
|
||||
public boolean isGeo() {
|
||||
return isGeo;
|
||||
return Type.GEO.equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns wheter the {@link IndexField} is a text index field.
|
||||
*
|
||||
* @return true if type is {@link Type#TEXT}
|
||||
* @since 1.6
|
||||
*/
|
||||
public boolean isText() {
|
||||
return Type.TEXT.equals(type);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -125,7 +155,7 @@ public final class IndexField {
|
||||
IndexField that = (IndexField) obj;
|
||||
|
||||
return this.key.equals(that.key) && ObjectUtils.nullSafeEquals(this.direction, that.direction)
|
||||
&& this.isGeo == that.isGeo;
|
||||
&& this.type == that.type;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -138,7 +168,8 @@ public final class IndexField {
|
||||
int result = 17;
|
||||
result += 31 * ObjectUtils.nullSafeHashCode(key);
|
||||
result += 31 * ObjectUtils.nullSafeHashCode(direction);
|
||||
result += 31 * ObjectUtils.nullSafeHashCode(isGeo);
|
||||
result += 31 * ObjectUtils.nullSafeHashCode(type);
|
||||
result += 31 * ObjectUtils.nullSafeHashCode(weight);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -148,6 +179,7 @@ public final class IndexField {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("IndexField [ key: %s, direction: %s, isGeo: %s]", key, direction, isGeo);
|
||||
return String.format("IndexField [ key: %s, direction: %s, type: %s, weight: %s]", key, direction, type, weight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -23,6 +23,11 @@ import java.util.List;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class IndexInfo {
|
||||
|
||||
private final List<IndexField> indexFields;
|
||||
@@ -31,14 +36,30 @@ public class IndexInfo {
|
||||
private final boolean unique;
|
||||
private final boolean dropDuplicates;
|
||||
private final boolean sparse;
|
||||
private final String language;
|
||||
|
||||
/**
|
||||
* @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)}
|
||||
* @param indexFields
|
||||
* @param name
|
||||
* @param unique
|
||||
* @param dropDuplicates
|
||||
* @param sparse
|
||||
*/
|
||||
@Deprecated
|
||||
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse) {
|
||||
this(indexFields, name, unique, dropDuplicates, sparse, "");
|
||||
}
|
||||
|
||||
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse,
|
||||
String language) {
|
||||
|
||||
this.indexFields = Collections.unmodifiableList(indexFields);
|
||||
this.name = name;
|
||||
this.unique = unique;
|
||||
this.dropDuplicates = dropDuplicates;
|
||||
this.sparse = sparse;
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,14 +105,23 @@ public class IndexInfo {
|
||||
return sparse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates="
|
||||
+ dropDuplicates + ", sparse=" + sparse + "]";
|
||||
+ dropDuplicates + ", sparse=" + sparse + ", language=" + language + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (dropDuplicates ? 1231 : 1237);
|
||||
@@ -99,6 +129,7 @@ public class IndexInfo {
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + (sparse ? 1231 : 1237);
|
||||
result = prime * result + (unique ? 1231 : 1237);
|
||||
result = prime * result + ObjectUtils.nullSafeHashCode(language);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -137,6 +168,9 @@ public class IndexInfo {
|
||||
if (unique != other.unique) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(language, other.language)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,12 @@
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -31,6 +30,9 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mongodb.core.index.Index.Duplicates;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.TextIndexIncludeOptions.IncludeStrategy;
|
||||
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
|
||||
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexedFieldSpec;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
@@ -95,6 +97,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root.getType()));
|
||||
indexInformation.addAll(potentiallyCreateTextIndexDefinition(root));
|
||||
|
||||
final CycleGuard guard = new CycleGuard();
|
||||
|
||||
@@ -103,15 +106,19 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
@Override
|
||||
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
|
||||
|
||||
if (persistentProperty.isEntity()) {
|
||||
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(),
|
||||
persistentProperty.getFieldName(), root.getCollection(), guard));
|
||||
}
|
||||
try {
|
||||
if (persistentProperty.isEntity()) {
|
||||
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(),
|
||||
persistentProperty.getFieldName(), root.getCollection(), guard));
|
||||
}
|
||||
|
||||
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(
|
||||
persistentProperty.getFieldName(), root.getCollection(), persistentProperty);
|
||||
if (indexDefinitionHolder != null) {
|
||||
indexInformation.add(indexDefinitionHolder);
|
||||
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(
|
||||
persistentProperty.getFieldName(), root.getCollection(), persistentProperty);
|
||||
if (indexDefinitionHolder != null) {
|
||||
indexInformation.add(indexDefinitionHolder);
|
||||
}
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -186,6 +193,82 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
return createCompoundIndexDefinitions(dotPath, collection, type);
|
||||
}
|
||||
|
||||
private Collection<? extends IndexDefinitionHolder> potentiallyCreateTextIndexDefinition(MongoPersistentEntity<?> root) {
|
||||
|
||||
TextIndexDefinitionBuilder indexDefinitionBuilder = new TextIndexDefinitionBuilder().named(root.getType()
|
||||
.getSimpleName() + "_TextIndex");
|
||||
|
||||
if (StringUtils.hasText(root.getLanguage())) {
|
||||
indexDefinitionBuilder.withDefaultLanguage(root.getLanguage());
|
||||
}
|
||||
|
||||
try {
|
||||
appendTextIndexInformation("", indexDefinitionBuilder, root,
|
||||
new TextIndexIncludeOptions(IncludeStrategy.DEFAULT), new CycleGuard());
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.warn(e.getMessage());
|
||||
}
|
||||
|
||||
TextIndexDefinition indexDefinition = indexDefinitionBuilder.build();
|
||||
|
||||
if (!indexDefinition.hasFieldSpec()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
IndexDefinitionHolder holder = new IndexDefinitionHolder("", indexDefinition, root.getCollection());
|
||||
return Collections.singletonList(holder);
|
||||
|
||||
}
|
||||
|
||||
private void appendTextIndexInformation(final String dotPath,
|
||||
final TextIndexDefinitionBuilder indexDefinitionBuilder, MongoPersistentEntity<?> entity,
|
||||
final TextIndexIncludeOptions includeOptions, final CycleGuard guard) {
|
||||
|
||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
||||
|
||||
@Override
|
||||
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
|
||||
|
||||
guard.protect(persistentProperty, dotPath);
|
||||
|
||||
if (persistentProperty.isLanguageProperty()) {
|
||||
indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName());
|
||||
}
|
||||
|
||||
TextIndexed indexed = persistentProperty.findAnnotation(TextIndexed.class);
|
||||
|
||||
if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) {
|
||||
|
||||
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "")
|
||||
+ persistentProperty.getFieldName();
|
||||
|
||||
Float weight = indexed != null ? indexed.weight()
|
||||
: (includeOptions.getParentFieldSpec() != null ? includeOptions.getParentFieldSpec().getWeight() : 1.0F);
|
||||
|
||||
if (persistentProperty.isEntity()) {
|
||||
|
||||
TextIndexIncludeOptions optionsForNestedType = includeOptions;
|
||||
if (!IncludeStrategy.FORCE.equals(includeOptions.getStrategy()) && indexed != null) {
|
||||
optionsForNestedType = new TextIndexIncludeOptions(IncludeStrategy.FORCE, new TextIndexedFieldSpec(
|
||||
propertyDotPath, weight));
|
||||
}
|
||||
|
||||
try {
|
||||
appendTextIndexInformation(propertyDotPath, indexDefinitionBuilder,
|
||||
mappingContext.getPersistentEntity(persistentProperty.getActualType()), optionsForNestedType, guard);
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.warn(e.getMessage(), e);
|
||||
}
|
||||
} else if (includeOptions.isForce() || indexed != null) {
|
||||
indexDefinitionBuilder.onField(propertyDotPath, weight);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} for {@link CompoundIndexes} of given type.
|
||||
*
|
||||
@@ -215,6 +298,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
return indexDefinitions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String fallbackCollection,
|
||||
CompoundIndex index) {
|
||||
|
||||
@@ -237,8 +321,14 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
indexDefinition.background();
|
||||
}
|
||||
|
||||
if (index.expireAfterSeconds() >= 0) {
|
||||
indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
|
||||
int ttl = index.expireAfterSeconds();
|
||||
|
||||
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;
|
||||
@@ -343,7 +433,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
private static class CycleGuard {
|
||||
static class CycleGuard {
|
||||
|
||||
private final Map<String, List<Path>> propertyTypeMap;
|
||||
|
||||
@@ -355,6 +445,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
* @param property The property to inspect
|
||||
* @param path The path under which the property can be reached.
|
||||
* @throws CyclicPropertyReferenceException in case a potential cycle is detected.
|
||||
* @see Path#cycles(MongoPersistentProperty, String)
|
||||
*/
|
||||
void protect(MongoPersistentProperty property, String path) throws CyclicPropertyReferenceException {
|
||||
|
||||
@@ -365,7 +456,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
for (Path existingPath : paths) {
|
||||
|
||||
if (existingPath.cycles(property)) {
|
||||
if (existingPath.cycles(property, path)) {
|
||||
paths.add(new Path(property, path));
|
||||
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
|
||||
existingPath.getPath());
|
||||
@@ -373,7 +464,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
}
|
||||
|
||||
paths.add(new Path(property, path));
|
||||
|
||||
} else {
|
||||
|
||||
ArrayList<Path> paths = new ArrayList<Path>();
|
||||
@@ -386,7 +476,30 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
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 String path;
|
||||
@@ -401,17 +514,23 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
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)?");
|
||||
Matcher matcher = pattern.matcher(path);
|
||||
|
||||
int count = 0;
|
||||
while (matcher.find()) {
|
||||
count++;
|
||||
if (!property.getOwner().equals(this.property.getOwner())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return count >= 1 && property.getOwner().getType().equals(this.property.getOwner().getType());
|
||||
return path.contains(this.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,8 +560,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
*/
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.format("Found cycle for field '%s' in type '%s' for path '%s'", propertyName, type.getSimpleName(),
|
||||
dotPath);
|
||||
return String.format("Found cycle for field '%s' in type '%s' for path '%s'", propertyName,
|
||||
type != null ? type.getSimpleName() : "unknown", dotPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,4 +630,41 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
return indexDefinition.getIndexOptions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
static class TextIndexIncludeOptions {
|
||||
|
||||
enum IncludeStrategy {
|
||||
FORCE, DEFAULT;
|
||||
}
|
||||
|
||||
private final IncludeStrategy strategy;
|
||||
|
||||
private final TextIndexedFieldSpec parentFieldSpec;
|
||||
|
||||
public TextIndexIncludeOptions(IncludeStrategy strategy, TextIndexedFieldSpec parentFieldSpec) {
|
||||
this.strategy = strategy;
|
||||
this.parentFieldSpec = parentFieldSpec;
|
||||
}
|
||||
|
||||
public TextIndexIncludeOptions(IncludeStrategy strategy) {
|
||||
this(strategy, null);
|
||||
}
|
||||
|
||||
public IncludeStrategy getStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public TextIndexedFieldSpec getParentFieldSpec() {
|
||||
return parentFieldSpec;
|
||||
}
|
||||
|
||||
public boolean isForce() {
|
||||
return IncludeStrategy.FORCE.equals(strategy);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* 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 java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* {@link IndexDefinition} to span multiple keys for text search.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
public class TextIndexDefinition implements IndexDefinition {
|
||||
|
||||
private String name;
|
||||
private Set<TextIndexedFieldSpec> fieldSpecs;
|
||||
private String defaultLanguage;
|
||||
private String languageOverride;
|
||||
|
||||
TextIndexDefinition() {
|
||||
fieldSpecs = new LinkedHashSet<TextIndexedFieldSpec>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TextIndexDefinition} for all fields in the document.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static TextIndexDefinition forAllFields() {
|
||||
return new TextIndexDefinitionBuilder().onAllFields().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link TextIndexDefinitionBuilder} to create {@link TextIndexDefinition}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static TextIndexDefinitionBuilder builder() {
|
||||
return new TextIndexDefinitionBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fieldSpec
|
||||
*/
|
||||
public void addFieldSpec(TextIndexedFieldSpec fieldSpec) {
|
||||
this.fieldSpecs.add(fieldSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fieldSpecs
|
||||
*/
|
||||
public void addFieldSpecs(Collection<TextIndexedFieldSpec> fieldSpecs) {
|
||||
this.fieldSpecs.addAll(fieldSpecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the {@link TextIndexDefinition} has fields assigned.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasFieldSpec() {
|
||||
return !fieldSpecs.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys()
|
||||
*/
|
||||
@Override
|
||||
public DBObject getIndexKeys() {
|
||||
|
||||
DBObject keys = new BasicDBObject();
|
||||
for (TextIndexedFieldSpec fieldSpec : fieldSpecs) {
|
||||
keys.put(fieldSpec.fieldname, "text");
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexOptions()
|
||||
*/
|
||||
@Override
|
||||
public DBObject getIndexOptions() {
|
||||
|
||||
DBObject options = new BasicDBObject();
|
||||
if (StringUtils.hasText(name)) {
|
||||
options.put("name", name);
|
||||
}
|
||||
if (StringUtils.hasText(defaultLanguage)) {
|
||||
options.put("default_language", defaultLanguage);
|
||||
}
|
||||
|
||||
BasicDBObject weightsDbo = new BasicDBObject();
|
||||
for (TextIndexedFieldSpec fieldSpec : fieldSpecs) {
|
||||
if (fieldSpec.isWeighted()) {
|
||||
weightsDbo.put(fieldSpec.getFieldname(), fieldSpec.getWeight());
|
||||
}
|
||||
}
|
||||
|
||||
if (!weightsDbo.isEmpty()) {
|
||||
options.put("weights", weightsDbo);
|
||||
}
|
||||
if (StringUtils.hasText(languageOverride)) {
|
||||
options.put("language_override", languageOverride);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
public static class TextIndexedFieldSpec {
|
||||
|
||||
private final String fieldname;
|
||||
private final Float weight;
|
||||
|
||||
/**
|
||||
* Create new {@link TextIndexedFieldSpec} for given fieldname without any weight.
|
||||
*
|
||||
* @param fieldname
|
||||
*/
|
||||
public TextIndexedFieldSpec(String fieldname) {
|
||||
this(fieldname, 1.0F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link TextIndexedFieldSpec} for given fieldname and weight.
|
||||
*
|
||||
* @param fieldname
|
||||
* @param weight
|
||||
*/
|
||||
public TextIndexedFieldSpec(String fieldname, Float weight) {
|
||||
|
||||
Assert.hasText(fieldname, "Text index field cannot be blank.");
|
||||
this.fieldname = fieldname;
|
||||
this.weight = weight != null ? weight : 1.0F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fieldname associated with the {@link TextIndexedFieldSpec}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getFieldname() {
|
||||
return fieldname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weight associated with the {@link TextIndexedFieldSpec}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Float getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if {@link #weight} has a value that is a valid number.
|
||||
*/
|
||||
public boolean isWeighted() {
|
||||
return this.weight != null && this.weight.compareTo(1.0F) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ObjectUtils.nullSafeHashCode(fieldname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof TextIndexedFieldSpec)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TextIndexedFieldSpec other = (TextIndexedFieldSpec) obj;
|
||||
|
||||
return ObjectUtils.nullSafeEquals(this.fieldname, other.fieldname);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TextIndexDefinitionBuilder} helps defining options for creating {@link TextIndexDefinition}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
public static class TextIndexDefinitionBuilder {
|
||||
|
||||
private TextIndexDefinition instance;
|
||||
private static final TextIndexedFieldSpec ALL_FIELDS = new TextIndexedFieldSpec("$**");
|
||||
|
||||
public TextIndexDefinitionBuilder() {
|
||||
this.instance = new TextIndexDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the name to be used when creating the index in the store.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public TextIndexDefinitionBuilder named(String name) {
|
||||
this.instance.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the index to span all fields using wilcard. <br/>
|
||||
* <strong>NOTE</strong> {@link TextIndexDefinition} cannot contain any other fields when defined with wildcard.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public TextIndexDefinitionBuilder onAllFields() {
|
||||
|
||||
if (!instance.fieldSpecs.isEmpty()) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot add wildcard fieldspect to non empty.");
|
||||
}
|
||||
|
||||
this.instance.fieldSpecs.add(ALL_FIELDS);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include given fields with default weight.
|
||||
*
|
||||
* @param fieldnames
|
||||
* @return
|
||||
*/
|
||||
public TextIndexDefinitionBuilder onFields(String... fieldnames) {
|
||||
|
||||
for (String fieldname : fieldnames) {
|
||||
onField(fieldname);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include given field with default weight.
|
||||
*
|
||||
* @param fieldname
|
||||
* @return
|
||||
*/
|
||||
public TextIndexDefinitionBuilder onField(String fieldname) {
|
||||
return onField(fieldname, Float.NaN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include given field with weight.
|
||||
*
|
||||
* @param fieldname
|
||||
* @return
|
||||
*/
|
||||
public TextIndexDefinitionBuilder onField(String fieldname, Float weight) {
|
||||
|
||||
if (this.instance.fieldSpecs.contains(ALL_FIELDS)) {
|
||||
throw new InvalidDataAccessApiUsageException(String.format("Cannot add %s to field spec for all fields.",
|
||||
fieldname));
|
||||
}
|
||||
|
||||
this.instance.fieldSpecs.add(new TextIndexedFieldSpec(fieldname, weight));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the default language to be used when indexing documents.
|
||||
*
|
||||
* @param language
|
||||
* @see http://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/#specify-default-language-text-index
|
||||
* @return
|
||||
*/
|
||||
public TextIndexDefinitionBuilder withDefaultLanguage(String language) {
|
||||
|
||||
this.instance.defaultLanguage = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define field for language override.
|
||||
*
|
||||
* @param fieldname
|
||||
* @return
|
||||
*/
|
||||
public TextIndexDefinitionBuilder withLanguageOverride(String fieldname) {
|
||||
|
||||
if (StringUtils.hasText(this.instance.languageOverride)) {
|
||||
throw new InvalidDataAccessApiUsageException(String.format(
|
||||
"Cannot set language override on %s as it is already defined on %s.", fieldname,
|
||||
this.instance.languageOverride));
|
||||
}
|
||||
|
||||
this.instance.languageOverride = fieldname;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextIndexDefinition build() {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* {@link TextIndexed} marks a field to be part of the text index. As there can be only one text index per collection
|
||||
* all fields marked with {@link TextIndexed} are combined into one single index. <br />
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
@Documented
|
||||
@Target({ ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface TextIndexed {
|
||||
|
||||
/**
|
||||
* Defines the significance of the filed relative to other indexed fields. The value directly influences the documents
|
||||
* score. <br/>
|
||||
* Defaulted to {@literal 1.0}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
float weight() default 1.0F;
|
||||
}
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,6 +38,7 @@ import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -47,12 +48,14 @@ import org.springframework.util.StringUtils;
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, MongoPersistentProperty> implements
|
||||
MongoPersistentEntity<T>, ApplicationContextAware {
|
||||
|
||||
private static final String AMBIGUOUS_FIELD_MAPPING = "Ambiguous field mapping detected! Both %s and %s map to the same field name %s! Disambiguate using @DocumentField annotation!";
|
||||
private final String collection;
|
||||
private final String language;
|
||||
private final SpelExpressionParser parser;
|
||||
private final StandardEvaluationContext context;
|
||||
|
||||
@@ -75,8 +78,10 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
if (rawType.isAnnotationPresent(Document.class)) {
|
||||
Document d = rawType.getAnnotation(Document.class);
|
||||
this.collection = StringUtils.hasText(d.collection()) ? d.collection() : fallback;
|
||||
this.language = StringUtils.hasText(d.language()) ? d.language() : "";
|
||||
} else {
|
||||
this.collection = fallback;
|
||||
this.language = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +105,15 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
return expression.getValue(context, String.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getLanguage()
|
||||
*/
|
||||
@Override
|
||||
public String getLanguage() {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.BasicPersistentEntity#verify()
|
||||
@@ -107,12 +121,22 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
@Override
|
||||
public void verify() {
|
||||
|
||||
verifyFieldUniqueness();
|
||||
verifyFieldTypes();
|
||||
}
|
||||
|
||||
private void verifyFieldUniqueness() {
|
||||
|
||||
AssertFieldNameUniquenessHandler handler = new AssertFieldNameUniquenessHandler();
|
||||
|
||||
doWithProperties(handler);
|
||||
doWithAssociations(handler);
|
||||
}
|
||||
|
||||
private void verifyFieldTypes() {
|
||||
doWithProperties(new PropertyTypeAssertionHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Comparator} implementation inspecting the {@link MongoPersistentProperty}'s order.
|
||||
*
|
||||
@@ -226,4 +250,46 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
properties.put(fieldName, property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
private static class PropertyTypeAssertionHandler implements PropertyHandler<MongoPersistentProperty> {
|
||||
|
||||
@Override
|
||||
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
|
||||
|
||||
potentiallyAssertTextScoreType(persistentProperty);
|
||||
potentiallyAssertLanguageType(persistentProperty);
|
||||
}
|
||||
|
||||
private void potentiallyAssertLanguageType(MongoPersistentProperty persistentProperty) {
|
||||
|
||||
if (persistentProperty.isLanguageProperty()) {
|
||||
assertPropertyType(persistentProperty, String.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void potentiallyAssertTextScoreType(MongoPersistentProperty persistentProperty) {
|
||||
|
||||
if (persistentProperty.isTextScoreProperty()) {
|
||||
assertPropertyType(persistentProperty, Float.class, Double.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPropertyType(MongoPersistentProperty persistentProperty, Class<?>... validMatches) {
|
||||
|
||||
for (Class<?> potentialMatch : validMatches) {
|
||||
if (ClassUtils.isAssignable(potentialMatch, persistentProperty.getActualType())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new MappingException(String.format("Missmatching types for %s. Found %s expected one of %s.",
|
||||
persistentProperty.getField(), persistentProperty.getActualType(),
|
||||
StringUtils.arrayToCommaDelimitedString(validMatches)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,7 +27,9 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -39,6 +41,7 @@ import com.mongodb.DBObject;
|
||||
* @author Oliver Gierke
|
||||
* @author Patryk Wasik
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class BasicMongoPersistentProperty extends AnnotationBasedPersistentProperty<MongoPersistentProperty> implements
|
||||
MongoPersistentProperty {
|
||||
@@ -46,6 +49,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BasicMongoPersistentProperty.class);
|
||||
|
||||
private static final String ID_FIELD_NAME = "_id";
|
||||
private static final String LANGUAGE_FIELD_NAME = "language";
|
||||
private static final Set<Class<?>> SUPPORTED_ID_TYPES = new HashSet<Class<?>>();
|
||||
private static final Set<String> SUPPORTED_ID_PROPERTY_NAMES = new HashSet<String>();
|
||||
|
||||
@@ -179,4 +183,22 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
public DBRef getDBRef() {
|
||||
return findAnnotation(DBRef.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isLanguageProperty()
|
||||
*/
|
||||
@Override
|
||||
public boolean isLanguageProperty() {
|
||||
return getFieldName().equals(LANGUAGE_FIELD_NAME) || isAnnotationPresent(Language.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isTextScoreProperty()
|
||||
*/
|
||||
@Override
|
||||
public boolean isTextScoreProperty() {
|
||||
return isAnnotationPresent(TextScore.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.mapping;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-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;
|
||||
|
||||
/**
|
||||
* {@link FieldNamingStrategy} that abbreviates field names by using the very first letter of the camel case parts of
|
||||
* the {@link MongoPersistentProperty}'s name.
|
||||
*
|
||||
* @since 1.3
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class CamelCaseAbbreviatingFieldNamingStrategy extends CamelCaseSplittingFieldNamingStrategy {
|
||||
|
||||
public CamelCaseAbbreviatingFieldNamingStrategy() {
|
||||
super("");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.CamelCaseSplittingFieldNamingStrategy#preparePart(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected String preparePart(String part) {
|
||||
return part.substring(0, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.util.ParsingUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Configurable {@link FieldNamingStrategy} that splits up camel-case property names and reconcatenates them using a
|
||||
* configured delimiter. Individual parts of the name can be manipulated using {@link #preparePart(String)}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.5
|
||||
*/
|
||||
public class CamelCaseSplittingFieldNamingStrategy implements FieldNamingStrategy {
|
||||
|
||||
private final String delimiter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CamelCaseSplittingFieldNamingStrategy}.
|
||||
*
|
||||
* @param delimiter must not be {@literal null}.
|
||||
*/
|
||||
public CamelCaseSplittingFieldNamingStrategy(String delimiter) {
|
||||
|
||||
Assert.notNull(delimiter, "Delimiter must not be null!");
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.FieldNamingStrategy#getFieldName(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
public String getFieldName(MongoPersistentProperty property) {
|
||||
|
||||
List<String> parts = ParsingUtils.splitCamelCaseToLower(property.getName());
|
||||
List<String> result = new ArrayList<String>();
|
||||
|
||||
for (String part : parts) {
|
||||
|
||||
String candidate = preparePart(part);
|
||||
|
||||
if (StringUtils.hasText(candidate)) {
|
||||
result.add(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
return StringUtils.collectionToDelimitedString(result, delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to prepare the uncapitalized part obtained from the split up of the camel case source. Default
|
||||
* implementation returns the part as is.
|
||||
*
|
||||
* @param part
|
||||
* @return
|
||||
*/
|
||||
protected String preparePart(String part) {
|
||||
return part;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011 by the original author(s).
|
||||
* Copyright (c) 2011-2014 by the original author(s).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,6 +29,7 @@ import org.springframework.data.annotation.Persistent;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* @author Oliver Gierke ogierke@vmware.com
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@Persistent
|
||||
@Inherited
|
||||
@@ -37,4 +38,13 @@ import org.springframework.data.annotation.Persistent;
|
||||
public @interface Document {
|
||||
|
||||
String collection() default "";
|
||||
|
||||
/**
|
||||
* Defines the default language to be used with this document.
|
||||
*
|
||||
* @since 1.6
|
||||
* @return
|
||||
*/
|
||||
String language() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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;
|
||||
|
||||
/**
|
||||
* SPI interface to determine how to name document fields in cases the field name is not manually defined.
|
||||
*
|
||||
* @see DocumentField
|
||||
* @see PropertyNameFieldNamingStrategy
|
||||
* @see CamelCaseAbbreviatingFieldNamingStrategy
|
||||
* @see SnakeCaseFieldNamingStrategy
|
||||
* @since 1.3
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface FieldNamingStrategy {
|
||||
|
||||
/**
|
||||
* Returns the field name to be used for the given {@link MongoPersistentProperty}.
|
||||
*
|
||||
* @param property must not be {@literal null} or empty;
|
||||
* @return
|
||||
*/
|
||||
String getFieldName(MongoPersistentProperty property);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* 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.
|
||||
@@ -15,20 +15,21 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping;
|
||||
|
||||
/**
|
||||
* {@link FieldNamingStrategy} that translates typical camel case Java property names to lower case JSON element names,
|
||||
* separated by underscores.
|
||||
*
|
||||
* @since 1.5
|
||||
* @author Ryan Tenney
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class SnakeCaseFieldNamingStrategy extends CamelCaseSplittingFieldNamingStrategy {
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Mark property as language field.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
@Documented
|
||||
@Target({ ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Language {
|
||||
|
||||
/**
|
||||
* Creates a new {@link SnakeCaseFieldNamingStrategy}.
|
||||
*/
|
||||
public SnakeCaseFieldNamingStrategy() {
|
||||
super("_");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,8 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import org.springframework.data.mapping.PersistentEntity;
|
||||
* MongoDB specific {@link PersistentEntity} abstraction.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersistentProperty> {
|
||||
|
||||
@@ -30,4 +31,13 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi
|
||||
* @return
|
||||
*/
|
||||
String getCollection();
|
||||
|
||||
/**
|
||||
* Returns the default language to be used for this entity.
|
||||
*
|
||||
* @since 1.6
|
||||
* @return
|
||||
*/
|
||||
String getLanguage();
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
* @author Oliver Gierke
|
||||
* @author Patryk Wasik
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface MongoPersistentProperty extends PersistentProperty<MongoPersistentProperty> {
|
||||
|
||||
@@ -59,6 +60,24 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
|
||||
*/
|
||||
boolean isExplicitIdProperty();
|
||||
|
||||
/**
|
||||
* Returns whether the property indicates the documents language either by having a {@link #getFieldName()} equal to
|
||||
* {@literal language} or being annotated with {@link Language}.
|
||||
*
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
boolean isLanguageProperty();
|
||||
|
||||
/**
|
||||
* Returns whether the property holds the documents score calculated by text search. <br/>
|
||||
* It's marked with {@link TextScore}.
|
||||
*
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
boolean isTextScoreProperty();
|
||||
|
||||
/**
|
||||
* Returns the {@link DBRef} if the property is a reference.
|
||||
*
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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;
|
||||
|
||||
/**
|
||||
* {@link FieldNamingStrategy} simply using the {@link MongoPersistentProperty}'s name.
|
||||
*
|
||||
* @since 1.3
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public enum PropertyNameFieldNamingStrategy implements FieldNamingStrategy {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.FieldNamingStrategy#getFieldName(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty)
|
||||
*/
|
||||
public String getFieldName(MongoPersistentProperty property) {
|
||||
return property.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
|
||||
/**
|
||||
* {@link TextScore} marks the property to be considered as the on server calculated {@literal textScore} when doing
|
||||
* full text search. <br />
|
||||
* <b>NOTE</b> Property will not be written when saving entity.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
@ReadOnlyProperty
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface TextScore {
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2011 the original author or authors.
|
||||
* Copyright 2010-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.
|
||||
@@ -24,11 +24,12 @@ import com.mongodb.util.JSON;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class BasicQuery extends Query {
|
||||
|
||||
private final DBObject queryObject;
|
||||
private final DBObject fieldsObject;
|
||||
private DBObject fieldsObject;
|
||||
private DBObject sortObject;
|
||||
|
||||
public BasicQuery(String query) {
|
||||
@@ -84,4 +85,12 @@ public class BasicQuery extends Query {
|
||||
public void setSortObject(DBObject sortObject) {
|
||||
this.sortObject = sortObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.6
|
||||
* @param fieldsObject
|
||||
*/
|
||||
protected void setFieldsObject(DBObject fieldsObject) {
|
||||
this.fieldsObject = fieldsObject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import static org.springframework.util.ObjectUtils.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -40,6 +41,7 @@ import com.mongodb.DBObject;
|
||||
* @author Oliver Gierke
|
||||
* @author Becca Gaspard
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class Update {
|
||||
|
||||
@@ -278,7 +280,36 @@ public class Update {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update given key to current date using {@literal $currentDate} modifier.
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/
|
||||
* @param key
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public Update currentDate(String key) {
|
||||
|
||||
addMultiFieldOperation("$currentDate", key, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update given key to current date using {@literal $currentDate : { $type : "timestamp" }} modifier.
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/
|
||||
* @param key
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
public Update currentTimestamp(String key) {
|
||||
|
||||
addMultiFieldOperation("$currentDate", key, new BasicDBObject("$type", "timestamp"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public DBObject getUpdateObject() {
|
||||
|
||||
DBObject dbo = new BasicDBObject();
|
||||
for (String k : modifierOps.keySet()) {
|
||||
dbo.put(k, modifierOps.get(k));
|
||||
@@ -335,14 +366,52 @@ public class Update {
|
||||
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 getUpdateObject().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifiers holds a distinct collection of {@link Modifier}
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public static class Modifiers {
|
||||
|
||||
private HashMap<String, Modifier> modifiers;
|
||||
private Map<String, Modifier> modifiers;
|
||||
|
||||
public Modifiers() {
|
||||
this.modifiers = new LinkedHashMap<String, Modifier>(1);
|
||||
@@ -355,6 +424,33 @@ public class Update {
|
||||
public void addModifier(Modifier 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 +475,7 @@ public class Update {
|
||||
* Implementation of {@link Modifier} representing {@code $each}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
private static class Each implements Modifier {
|
||||
|
||||
@@ -399,6 +496,7 @@ public class Update {
|
||||
}
|
||||
|
||||
Object[] convertedValues = new Object[values.length];
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
convertedValues[i] = values[i];
|
||||
}
|
||||
@@ -406,21 +504,57 @@ public class Update {
|
||||
return convertedValues;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey()
|
||||
*/
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "$each";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue()
|
||||
*/
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return this.values;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return nullSafeHashCode(values);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object that) {
|
||||
|
||||
if (this == that) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (that == null || getClass() != that.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return nullSafeEquals(values, ((Each) that).values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating {@code $push} modifiers
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class PushOperatorBuilder {
|
||||
|
||||
@@ -453,6 +587,50 @@ public class Update {
|
||||
public Update value(Object 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 +666,5 @@ public class Update {
|
||||
public Update value(Object value) {
|
||||
return Update.this.addToSet(this.key, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.query.text;
|
||||
|
||||
/**
|
||||
* A {@link Term} defines one or multiple words {@link Type#WORD} or phrases {@link Type#PHRASE} to be used in the
|
||||
* context of full text search.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
public class Term {
|
||||
|
||||
enum Type {
|
||||
WORD, PHRASE;
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final String raw;
|
||||
private boolean negated;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Term} of {@link Type#WORD}.
|
||||
*
|
||||
* @param raw
|
||||
*/
|
||||
public Term(String raw) {
|
||||
this(raw, Type.WORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Term} of given {@link Type}.
|
||||
*
|
||||
* @param raw
|
||||
* @param type defaulted to {@link Type#WORD} if {@literal null}.
|
||||
*/
|
||||
public Term(String raw, Type type) {
|
||||
this.raw = raw;
|
||||
this.type = type == null ? Type.WORD : type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates the term.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Term negate() {
|
||||
this.negated = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return true if term is negated.
|
||||
*/
|
||||
public boolean isNegated() {
|
||||
return negated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return type of term. Never {@literal null}.
|
||||
*/
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted representation of term.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getFormatted() {
|
||||
|
||||
String formatted = Type.PHRASE.equals(type) ? quotePhrase(raw) : raw;
|
||||
return negated ? negateRaw(formatted) : formatted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFormatted();
|
||||
}
|
||||
|
||||
protected String quotePhrase(String raw) {
|
||||
return "\"" + raw + "\"";
|
||||
}
|
||||
|
||||
protected String negateRaw(String raw) {
|
||||
return "-" + raw;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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.query.text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
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.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.BasicDBObjectBuilder;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Implementation of {@link CriteriaDefinition} to be used for full text search .
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
public class TextCriteria extends Criteria {
|
||||
|
||||
private String language;
|
||||
private List<Term> terms;
|
||||
|
||||
public TextCriteria() {
|
||||
this.terms = new ArrayList<Term>();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.CriteriaDefinition#getCriteriaObject()
|
||||
*/
|
||||
@Override
|
||||
public DBObject getCriteriaObject() {
|
||||
|
||||
BasicDBObjectBuilder builder = new BasicDBObjectBuilder();
|
||||
|
||||
if (StringUtils.hasText(language)) {
|
||||
builder.add("$language", language);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(terms)) {
|
||||
builder.add("$search", join(terms.iterator()));
|
||||
}
|
||||
|
||||
return new BasicDBObject("$text", builder.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param words
|
||||
* @return
|
||||
*/
|
||||
public TextCriteria matchingAny(String... words) {
|
||||
|
||||
for (String word : words) {
|
||||
matching(word);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add given {@link Term} to criteria.
|
||||
*
|
||||
* @param term must not be null.
|
||||
*/
|
||||
public void matching(Term term) {
|
||||
|
||||
Assert.notNull(term, "Term to add must not be null.");
|
||||
this.terms.add(term);
|
||||
}
|
||||
|
||||
private void notMatching(Term term) {
|
||||
matching(term.negate());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param term
|
||||
* @return
|
||||
*/
|
||||
public TextCriteria matching(String term) {
|
||||
|
||||
if (StringUtils.hasText(term)) {
|
||||
matching(new Term(term));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param term
|
||||
* @return
|
||||
*/
|
||||
public TextCriteria notMatching(String term) {
|
||||
|
||||
if (StringUtils.hasText(term)) {
|
||||
notMatching(new Term(term, Term.Type.WORD));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param words
|
||||
* @return
|
||||
*/
|
||||
public TextCriteria notMatchingAny(String... words) {
|
||||
|
||||
for (String word : words) {
|
||||
notMatching(word);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given value will treated as a single phrase.
|
||||
*
|
||||
* @param phrase
|
||||
* @return
|
||||
*/
|
||||
public TextCriteria notMatchingPhrase(String phrase) {
|
||||
|
||||
if (StringUtils.hasText(phrase)) {
|
||||
notMatching(new Term(phrase, Term.Type.PHRASE));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given value will treated as a single phrase.
|
||||
*
|
||||
* @param phrase
|
||||
* @return
|
||||
*/
|
||||
public TextCriteria matchingPhrase(String phrase) {
|
||||
|
||||
if (StringUtils.hasText(phrase)) {
|
||||
matching(new Term(phrase, Term.Type.PHRASE));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public static TextCriteria forDefaultLanguage() {
|
||||
return new TextCriteriaBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* For a full list of supported languages see the mongdodb reference manual for <a
|
||||
* href="http://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages</a>.
|
||||
*
|
||||
* @param language
|
||||
* @return
|
||||
*/
|
||||
public static TextCriteria forLanguage(String language) {
|
||||
return new TextCriteriaBuilder().withLanguage(language).build();
|
||||
}
|
||||
|
||||
private static String join(Iterator<Term> iterator) {
|
||||
|
||||
Term first = iterator.next();
|
||||
if (!iterator.hasNext()) {
|
||||
return first.getFormatted();
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
if (first != null) {
|
||||
buf.append(first);
|
||||
}
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
buf.append(' ');
|
||||
Term obj = iterator.next();
|
||||
if (obj != null) {
|
||||
buf.append(obj.getFormatted());
|
||||
}
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static class TextCriteriaBuilder {
|
||||
|
||||
private TextCriteria instance;
|
||||
|
||||
public TextCriteriaBuilder() {
|
||||
this.instance = new TextCriteria();
|
||||
}
|
||||
|
||||
public TextCriteriaBuilder withLanguage(String language) {
|
||||
this.instance.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextCriteria build() {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "$text";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.query.text;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* {@link Query} implementation to be used to for performing full text searches.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
public class TextQuery extends Query {
|
||||
|
||||
private final String DEFAULT_SCORE_FIELD_FIELDNAME = "score";
|
||||
private final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore");
|
||||
|
||||
private String scoreFieldName = DEFAULT_SCORE_FIELD_FIELDNAME;
|
||||
private boolean includeScore = false;
|
||||
private boolean sortByScore = false;
|
||||
|
||||
/**
|
||||
* Creates new {@link TextQuery} using the the given {@code wordsAndPhrases} with {@link TextCriteria}
|
||||
*
|
||||
* @param wordsAndPhrases
|
||||
* @see TextCriteria#matching(String)
|
||||
*/
|
||||
public TextQuery(String wordsAndPhrases) {
|
||||
super(TextCriteria.forDefaultLanguage().matching(wordsAndPhrases));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link TextQuery} in {@code language}. <br />
|
||||
* For a full list of supported languages see the mongdodb reference manual for <a
|
||||
* href="http://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages</a>.
|
||||
*
|
||||
* @param wordsAndPhrases
|
||||
* @param language
|
||||
* @see TextCriteria#forLanguage(String)
|
||||
* @see TextCriteria#matching(String)
|
||||
*/
|
||||
public TextQuery(String wordsAndPhrases, String language) {
|
||||
super(TextCriteria.forLanguage(language).matching(wordsAndPhrases));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link TextQuery} using the {@code locale}s language.<br />
|
||||
* For a full list of supported languages see the mongdodb reference manual for <a
|
||||
* href="http://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages</a>.
|
||||
*
|
||||
* @param wordsAndPhrases
|
||||
* @param locale
|
||||
*/
|
||||
public TextQuery(String wordsAndPhrases, Locale locale) {
|
||||
this(wordsAndPhrases, locale != null ? locale.getLanguage() : (String) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link TextQuery} for given {@link TextCriteria}.
|
||||
*
|
||||
* @param criteria.
|
||||
*/
|
||||
public TextQuery(TextCriteria criteria) {
|
||||
super(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link TextQuery} searching for given {@link TextCriteria}.
|
||||
*
|
||||
* @param criteria
|
||||
* @return
|
||||
*/
|
||||
public static TextQuery queryText(TextCriteria criteria) {
|
||||
return new TextQuery(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add sorting by text score. Will also add text score to returned fields.
|
||||
*
|
||||
* @see TextQuery#includeScore()
|
||||
* @return
|
||||
*/
|
||||
public TextQuery sortByScore() {
|
||||
|
||||
this.includeScore();
|
||||
this.sortByScore = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add field {@literal score} holding the documents textScore to the returned fields.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public TextQuery includeScore() {
|
||||
|
||||
this.includeScore = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include text search document score in returned fields using the given fieldname.
|
||||
*
|
||||
* @param fieldname
|
||||
* @return
|
||||
*/
|
||||
public TextQuery includeScore(String fieldname) {
|
||||
|
||||
setScoreFieldName(fieldname);
|
||||
includeScore();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fieldname used for scoring.
|
||||
*
|
||||
* @param fieldName
|
||||
*/
|
||||
public void setScoreFieldName(String fieldName) {
|
||||
this.scoreFieldName = fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fieldname used for scoring
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getScoreFieldName() {
|
||||
return scoreFieldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBObject getFieldsObject() {
|
||||
|
||||
if (!this.includeScore) {
|
||||
return super.getFieldsObject();
|
||||
}
|
||||
|
||||
DBObject fields = super.getFieldsObject();
|
||||
if (fields == null) {
|
||||
fields = new BasicDBObject();
|
||||
}
|
||||
fields.put(getScoreFieldName(), META_TEXT_SCORE);
|
||||
return fields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBObject getSortObject() {
|
||||
|
||||
DBObject sort = new BasicDBObject();
|
||||
if (this.sortByScore) {
|
||||
sort.put(getScoreFieldName(), META_TEXT_SCORE);
|
||||
}
|
||||
if (super.getSortObject() != null) {
|
||||
sort.putAll(super.getSortObject());
|
||||
}
|
||||
return sort;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
@@ -254,10 +255,26 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
Object execute(Query query) {
|
||||
|
||||
MongoEntityMetadata<?> metadata = method.getEntityInformation();
|
||||
|
||||
int overallLimit = query.getLimit();
|
||||
long count = operations.count(query, metadata.getCollectionName());
|
||||
count = overallLimit != 0 ? Math.min(count, query.getLimit()) : count;
|
||||
|
||||
List<?> result = operations.find(query.with(pageable), metadata.getJavaType(), metadata.getCollectionName());
|
||||
boolean pageableOutOfScope = pageable.getOffset() > query.getLimit();
|
||||
|
||||
if (pageableOutOfScope) {
|
||||
return new PageImpl<Object>(Collections.emptyList(), pageable, count);
|
||||
}
|
||||
|
||||
// Apply raw pagination
|
||||
query = query.with(pageable);
|
||||
|
||||
// Adjust limit if page would exceed the overall limit
|
||||
if (overallLimit != 0 && pageable.getOffset() + pageable.getPageSize() > overallLimit) {
|
||||
query.limit(overallLimit - pageable.getOffset());
|
||||
}
|
||||
|
||||
List<?> result = operations.find(query, metadata.getJavaType(), metadata.getCollectionName());
|
||||
return new PageImpl(result, pageable, count);
|
||||
}
|
||||
}
|
||||
@@ -391,7 +408,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,14 @@ import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
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.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.util.JSONParseException;
|
||||
|
||||
/**
|
||||
* {@link RepositoryQuery} implementation for Mongo.
|
||||
@@ -67,7 +71,28 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
||||
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, accessor, context, isGeoNearQuery);
|
||||
return creator.createQuery();
|
||||
Query query = creator.createQuery();
|
||||
|
||||
if (tree.isLimiting()) {
|
||||
query.limit(tree.getMaxResults());
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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");
|
||||
* 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 com.mongodb.DBObject;
|
||||
import com.mongodb.DBRef;
|
||||
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.PathMetadata;
|
||||
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.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class SpringDataMongodbSerializer extends MongodbSerializer {
|
||||
|
||||
private final String ID_KEY = "_id";
|
||||
|
||||
private final MongoConverter converter;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final QueryMapper mapper;
|
||||
@@ -44,7 +50,7 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
|
||||
/**
|
||||
* Creates a new {@link SpringDataMongodbSerializer} for the given {@link MappingContext}.
|
||||
*
|
||||
* @param mappingContext
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*/
|
||||
public SpringDataMongodbSerializer(MongoConverter converter) {
|
||||
|
||||
@@ -80,10 +86,63 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
|
||||
@Override
|
||||
protected DBObject asDBObject(String key, Object value) {
|
||||
|
||||
if ("_id".equals(key)) {
|
||||
return super.asDBObject(key, mapper.convertId(value));
|
||||
if (ID_KEY.equals(key)) {
|
||||
return mapper.getMappedObject(super.asDBObject(key, value), null);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mongodb.core.convert.CustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoTypeMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.Account;
|
||||
import org.springframework.data.mongodb.core.mapping.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
@@ -2693,6 +2693,22 @@ public class MongoTemplateTests {
|
||||
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 {
|
||||
|
||||
@Id String someIdKey;
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.convert.CustomConversions;
|
||||
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.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.BasicDBObjectBuilder;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
@@ -329,6 +332,26 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
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 {
|
||||
|
||||
@Id BigInteger id;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AggregationOptions}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.6
|
||||
*/
|
||||
public class AggregationOptionsTests {
|
||||
|
||||
AggregationOptions aggregationOptions;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
aggregationOptions = newAggregationOptions().explain(true).cursor(new BasicDBObject("foo", 1)).allowDiskUse(true)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-960
|
||||
*/
|
||||
@Test
|
||||
public void aggregationOptionsBuilderShouldSetOptionsAccordingly() {
|
||||
|
||||
assertThat(aggregationOptions.isAllowDiskUse(), is(true));
|
||||
assertThat(aggregationOptions.isExplain(), is(true));
|
||||
assertThat(aggregationOptions.getCursor(), is((DBObject) new BasicDBObject("foo", 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-960
|
||||
*/
|
||||
@Test
|
||||
public void aggregationOptionsToString() {
|
||||
assertThat(aggregationOptions.toString(),
|
||||
is("{ \"allowDiskUse\" : true , \"explain\" : true , \"cursor\" : { \"foo\" : 1}}"));
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -44,11 +46,14 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.mongodb.core.CollectionCallback;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.data.util.Version;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -75,6 +80,7 @@ public class AggregationTests {
|
||||
private static final String INPUT_COLLECTION = "aggregation_test_collection";
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AggregationTests.class);
|
||||
private static final Version TWO_DOT_FOUR = new Version(2, 4);
|
||||
private static final Version TWO_DOT_SIX = new Version(2, 6);
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
@@ -112,6 +118,8 @@ public class AggregationTests {
|
||||
mongoTemplate.dropCollection(Data.class);
|
||||
mongoTemplate.dropCollection(DATAMONGO788.class);
|
||||
mongoTemplate.dropCollection(User.class);
|
||||
mongoTemplate.dropCollection(Person.class);
|
||||
mongoTemplate.dropCollection(Reservation.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,23 +422,7 @@ public class AggregationTests {
|
||||
|
||||
createUserWithLikesDocuments();
|
||||
|
||||
/*
|
||||
...
|
||||
$group: {
|
||||
_id:"$like",
|
||||
number:{ $sum:1}
|
||||
}
|
||||
...
|
||||
|
||||
*/
|
||||
|
||||
TypedAggregation<UserWithLikes> agg = newAggregation(UserWithLikes.class, //
|
||||
unwind("likes"), //
|
||||
group("likes").count().as("number"), //
|
||||
sort(DESC, "number"), //
|
||||
limit(5), //
|
||||
sort(ASC, previousOperation()) //
|
||||
);
|
||||
TypedAggregation<UserWithLikes> agg = createUsersWithCommonLikesAggregation();
|
||||
|
||||
assertThat(agg, is(notNullValue()));
|
||||
assertThat(agg.toString(), is(notNullValue()));
|
||||
@@ -447,6 +439,16 @@ public class AggregationTests {
|
||||
assertLikeStats(result.getMappedResults().get(4), "e", 3);
|
||||
}
|
||||
|
||||
protected TypedAggregation<UserWithLikes> createUsersWithCommonLikesAggregation() {
|
||||
return newAggregation(UserWithLikes.class, //
|
||||
unwind("likes"), //
|
||||
group("likes").count().as("number"), //
|
||||
sort(DESC, "number"), //
|
||||
limit(5), //
|
||||
sort(ASC, previousOperation()) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arithmenticOperatorsInProjectionExample() {
|
||||
|
||||
@@ -857,6 +859,165 @@ public class AggregationTests {
|
||||
assertThat(result.getMappedResults(), hasSize(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-960
|
||||
*/
|
||||
@Test
|
||||
public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabled() {
|
||||
|
||||
assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX));
|
||||
|
||||
createUserWithLikesDocuments();
|
||||
|
||||
TypedAggregation<UserWithLikes> agg = createUsersWithCommonLikesAggregation() //
|
||||
.withOptions(newAggregationOptions().allowDiskUse(true).build());
|
||||
|
||||
assertThat(agg, is(notNullValue()));
|
||||
assertThat(agg.toString(), is(notNullValue()));
|
||||
|
||||
AggregationResults<LikeStats> result = mongoTemplate.aggregate(agg, LikeStats.class);
|
||||
assertThat(result, is(notNullValue()));
|
||||
assertThat(result.getMappedResults(), is(notNullValue()));
|
||||
assertThat(result.getMappedResults().size(), is(5));
|
||||
|
||||
assertLikeStats(result.getMappedResults().get(0), "a", 4);
|
||||
assertLikeStats(result.getMappedResults().get(1), "b", 2);
|
||||
assertLikeStats(result.getMappedResults().get(2), "c", 4);
|
||||
assertLikeStats(result.getMappedResults().get(3), "d", 2);
|
||||
assertLikeStats(result.getMappedResults().get(4), "e", 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-960
|
||||
*/
|
||||
@Test
|
||||
public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() {
|
||||
|
||||
assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX));
|
||||
|
||||
createUserWithLikesDocuments();
|
||||
|
||||
TypedAggregation<UserWithLikes> agg = createUsersWithCommonLikesAggregation() //
|
||||
.withOptions(newAggregationOptions().explain(true).build());
|
||||
|
||||
AggregationResults<LikeStats> result = mongoTemplate.aggregate(agg, LikeStats.class);
|
||||
|
||||
assertThat(result.getMappedResults(), is(empty()));
|
||||
|
||||
DBObject rawResult = result.getRawResults();
|
||||
|
||||
assertThat(rawResult, is(notNullValue()));
|
||||
assertThat(rawResult.containsField("stages"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-954
|
||||
*/
|
||||
@Test
|
||||
public void shouldSupportReturningCurrentAggregationRoot() {
|
||||
|
||||
assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX));
|
||||
|
||||
mongoTemplate.save(new Person("p1_first", "p1_last", 25));
|
||||
mongoTemplate.save(new Person("p2_first", "p2_last", 32));
|
||||
mongoTemplate.save(new Person("p3_first", "p3_last", 25));
|
||||
mongoTemplate.save(new Person("p4_first", "p4_last", 15));
|
||||
|
||||
List<DBObject> personsWithAge25 = mongoTemplate.find(Query.query(where("age").is(25)), DBObject.class,
|
||||
mongoTemplate.getCollectionName(Person.class));
|
||||
|
||||
Aggregation agg = newAggregation(group("age").push(Aggregation.ROOT).as("users"));
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, Person.class, DBObject.class);
|
||||
|
||||
assertThat(result.getMappedResults(), hasSize(3));
|
||||
DBObject o = (DBObject) result.getMappedResults().get(2);
|
||||
|
||||
assertThat(o.get("_id"), is((Object) 25));
|
||||
assertThat((List<?>) o.get("users"), hasSize(2));
|
||||
assertThat((List<?>) o.get("users"), is(contains(personsWithAge25.toArray())));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-954
|
||||
* @see http
|
||||
* ://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document
|
||||
*/
|
||||
@Test
|
||||
public void shouldSupportReturningCurrentAggregationRootInReference() {
|
||||
|
||||
assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX));
|
||||
|
||||
mongoTemplate.save(new Reservation("0123", "42", 100));
|
||||
mongoTemplate.save(new Reservation("0360", "43", 200));
|
||||
mongoTemplate.save(new Reservation("0360", "44", 300));
|
||||
|
||||
Aggregation agg = newAggregation( //
|
||||
match(where("hotelCode").is("0360")), //
|
||||
sort(Direction.DESC, "confirmationNumber", "timestamp"), //
|
||||
group("confirmationNumber") //
|
||||
.first("timestamp").as("timestamp") //
|
||||
.first(Aggregation.ROOT).as("reservationImage") //
|
||||
);
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class);
|
||||
|
||||
assertThat(result.getMappedResults(), hasSize(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-975
|
||||
*/
|
||||
@Test
|
||||
public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception {
|
||||
|
||||
mongoTemplate.dropCollection(ObjectWithDate.class);
|
||||
|
||||
DateTime dateTime = new DateTime() //
|
||||
.withYear(2014) //
|
||||
.withMonthOfYear(2) //
|
||||
.withDayOfMonth(7) //
|
||||
.withTime(3, 4, 5, 6).toDateTime(DateTimeZone.UTC).toDateTimeISO();
|
||||
|
||||
ObjectWithDate owd = new ObjectWithDate(dateTime.toDate());
|
||||
mongoTemplate.insert(owd);
|
||||
|
||||
ProjectionOperation dateProjection = Aggregation.project() //
|
||||
.and("dateValue").extractHour().as("hour") //
|
||||
.and("dateValue").extractMinute().as("min") //
|
||||
.and("dateValue").extractSecond().as("second") //
|
||||
.and("dateValue").extractMillisecond().as("millis") //
|
||||
.and("dateValue").extractYear().as("year") //
|
||||
.and("dateValue").extractMonth().as("month") //
|
||||
.and("dateValue").extractWeek().as("week") //
|
||||
.and("dateValue").extractDayOfYear().as("dayOfYear") //
|
||||
.and("dateValue").extractDayOfMonth().as("dayOfMonth") //
|
||||
.and("dateValue").extractDayOfWeek().as("dayOfWeek") //
|
||||
.andExpression("dateValue + 86400000").extractDayOfYear().as("dayOfYearPlus1Day") //
|
||||
.andExpression("dateValue + 86400000").project("dayOfYear").as("dayOfYearPlus1DayManually") //
|
||||
;
|
||||
|
||||
Aggregation agg = newAggregation(dateProjection);
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, ObjectWithDate.class, DBObject.class);
|
||||
|
||||
assertThat(result.getMappedResults(), hasSize(1));
|
||||
DBObject dbo = result.getMappedResults().get(0);
|
||||
|
||||
assertThat(dbo.get("hour"), is((Object) dateTime.getHourOfDay()));
|
||||
assertThat(dbo.get("min"), is((Object) dateTime.getMinuteOfHour()));
|
||||
assertThat(dbo.get("second"), is((Object) dateTime.getSecondOfMinute()));
|
||||
assertThat(dbo.get("millis"), is((Object) dateTime.getMillisOfSecond()));
|
||||
assertThat(dbo.get("year"), is((Object) dateTime.getYear()));
|
||||
assertThat(dbo.get("month"), is((Object) dateTime.getMonthOfYear()));
|
||||
// dateTime.getWeekOfWeekyear()) returns 6 since for MongoDB the week starts on sunday and not on monday.
|
||||
assertThat(dbo.get("week"), is((Object) 5));
|
||||
assertThat(dbo.get("dayOfYear"), is((Object) dateTime.getDayOfYear()));
|
||||
assertThat(dbo.get("dayOfMonth"), is((Object) dateTime.getDayOfMonth()));
|
||||
|
||||
// dateTime.getDayOfWeek()
|
||||
assertThat(dbo.get("dayOfWeek"), is((Object) 6));
|
||||
assertThat(dbo.get("dayOfYearPlus1Day"), is((Object) dateTime.plusDays(1).getDayOfYear()));
|
||||
assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear()));
|
||||
}
|
||||
|
||||
private void assertLikeStats(LikeStats like, String id, long count) {
|
||||
|
||||
assertThat(like, is(notNullValue()));
|
||||
@@ -992,6 +1153,7 @@ public class AggregationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Descriptors {
|
||||
private CarDescriptor carDescriptor;
|
||||
}
|
||||
@@ -1007,6 +1169,7 @@ public class AggregationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Entry {
|
||||
private String make;
|
||||
private String model;
|
||||
@@ -1021,4 +1184,28 @@ public class AggregationTests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Reservation {
|
||||
|
||||
String hotelCode;
|
||||
String confirmationNumber;
|
||||
int timestamp;
|
||||
|
||||
public Reservation() {}
|
||||
|
||||
public Reservation(String hotelCode, String confirmationNumber, int timestamp) {
|
||||
this.hotelCode = hotelCode;
|
||||
this.confirmationNumber = confirmationNumber;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
static class ObjectWithDate {
|
||||
|
||||
Date dateValue;
|
||||
|
||||
public ObjectWithDate(Date dateValue) {
|
||||
this.dateValue = dateValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.List;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
@@ -219,6 +220,69 @@ public class AggregationUnitTests {
|
||||
assertThat(projection1, is((DBObject) new BasicDBObject("b", "$ba")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-960
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderAggregationWithDefaultOptionsCorrectly() {
|
||||
|
||||
DBObject agg = newAggregation( //
|
||||
project().and("a").as("aa") //
|
||||
).toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg.toString(),
|
||||
is("{ \"aggregate\" : \"foo\" , \"pipeline\" : [ { \"$project\" : { \"aa\" : \"$a\"}}]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-960
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderAggregationWithCustomOptionsCorrectly() {
|
||||
|
||||
AggregationOptions aggregationOptions = newAggregationOptions().explain(true).cursor(new BasicDBObject("foo", 1))
|
||||
.allowDiskUse(true).build();
|
||||
|
||||
DBObject agg = newAggregation( //
|
||||
project().and("a").as("aa") //
|
||||
) //
|
||||
.withOptions(aggregationOptions) //
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg.toString(), is("{ \"aggregate\" : \"foo\" , " //
|
||||
+ "\"pipeline\" : [ { \"$project\" : { \"aa\" : \"$a\"}}] , " //
|
||||
+ "\"allowDiskUse\" : true , " //
|
||||
+ "\"explain\" : true , " //
|
||||
+ "\"cursor\" : { \"foo\" : 1}}" //
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-954
|
||||
*/
|
||||
@Test
|
||||
public void shouldSupportReferencingSystemVariables() {
|
||||
|
||||
DBObject agg = newAggregation( //
|
||||
project("someKey") //
|
||||
.and("a").as("a1") //
|
||||
.and(Aggregation.CURRENT + ".a").as("a2") //
|
||||
, sort(Direction.DESC, "a") //
|
||||
, group("someKey").first(Aggregation.ROOT).as("doc") //
|
||||
).toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject projection0 = extractPipelineElement(agg, 0, "$project");
|
||||
assertThat(projection0, is((DBObject) new BasicDBObject("someKey", 1).append("a1", "$a")
|
||||
.append("a2", "$$CURRENT.a")));
|
||||
|
||||
DBObject sort = extractPipelineElement(agg, 1, "$sort");
|
||||
assertThat(sort, is((DBObject) new BasicDBObject("a", -1)));
|
||||
|
||||
DBObject group = extractPipelineElement(agg, 2, "$group");
|
||||
assertThat(group,
|
||||
is((DBObject) new BasicDBObject("_id", "$someKey").append("doc", new BasicDBObject("$first", "$$ROOT"))));
|
||||
}
|
||||
|
||||
private DBObject extractPipelineElement(DBObject agg, int index, String operation) {
|
||||
|
||||
List<DBObject> pipeline = (List<DBObject>) agg.get("pipeline");
|
||||
|
||||
@@ -20,12 +20,12 @@ import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.util.DBObjectUtils.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.DBObjectTestUtils;
|
||||
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
@@ -91,7 +91,7 @@ public class ProjectionOperationUnitTests {
|
||||
DBObject dbObject = operation.and("foo").plus(41).as("bar").toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
|
||||
DBObject barClause = DBObjectTestUtils.getAsDBObject(projectClause, "bar");
|
||||
BasicDBList addClause = DBObjectTestUtils.getAsDBList(barClause, "$add");
|
||||
List<Object> addClause = (List<Object>) barClause.get("$add");
|
||||
|
||||
assertThat(addClause, hasSize(2));
|
||||
assertThat(addClause.get(0), is((Object) "$foo"));
|
||||
@@ -276,6 +276,64 @@ public class ProjectionOperationUnitTests {
|
||||
is("{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-975
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderDateTimeFragmentExtractionsForSimpleFieldProjectionsCorrectly() {
|
||||
|
||||
ProjectionOperation operation = Aggregation.project() //
|
||||
.and("date").extractHour().as("hour") //
|
||||
.and("date").extractMinute().as("min") //
|
||||
.and("date").extractSecond().as("second") //
|
||||
.and("date").extractMillisecond().as("millis") //
|
||||
.and("date").extractYear().as("year") //
|
||||
.and("date").extractMonth().as("month") //
|
||||
.and("date").extractWeek().as("week") //
|
||||
.and("date").extractDayOfYear().as("dayOfYear") //
|
||||
.and("date").extractDayOfMonth().as("dayOfMonth") //
|
||||
.and("date").extractDayOfWeek().as("dayOfWeek") //
|
||||
;
|
||||
|
||||
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
assertThat(dbObject, is(notNullValue()));
|
||||
|
||||
DBObject projected = exctractOperation("$project", dbObject);
|
||||
|
||||
assertThat(projected.get("hour"), is((Object) new BasicDBObject("$hour", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("min"), is((Object) new BasicDBObject("$minute", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("second"), is((Object) new BasicDBObject("$second", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("millis"), is((Object) new BasicDBObject("$millisecond", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("year"), is((Object) new BasicDBObject("$year", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("month"), is((Object) new BasicDBObject("$month", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("week"), is((Object) new BasicDBObject("$week", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("dayOfYear"), is((Object) new BasicDBObject("$dayOfYear", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("dayOfMonth"), is((Object) new BasicDBObject("$dayOfMonth", Arrays.asList("$date"))));
|
||||
assertThat(projected.get("dayOfWeek"), is((Object) new BasicDBObject("$dayOfWeek", Arrays.asList("$date"))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-975
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderDateTimeFragmentExtractionsForExpressionProjectionsCorrectly() throws Exception {
|
||||
|
||||
ProjectionOperation operation = Aggregation.project() //
|
||||
.andExpression("date + 86400000") //
|
||||
.extractDayOfYear() //
|
||||
.as("dayOfYearPlus1Day") //
|
||||
;
|
||||
|
||||
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
assertThat(dbObject, is(notNullValue()));
|
||||
|
||||
DBObject projected = exctractOperation("$project", dbObject);
|
||||
assertThat(
|
||||
projected.get("dayOfYearPlus1Day"),
|
||||
is((Object) new BasicDBObject("$dayOfYear", Arrays.asList(new BasicDBObject("$add", Arrays.<Object> asList(
|
||||
"$date", 86400000))))));
|
||||
}
|
||||
|
||||
private static DBObject exctractOperation(String field, DBObject fromProjectClause) {
|
||||
return (DBObject) fromProjectClause.get(field);
|
||||
}
|
||||
|
||||
@@ -146,6 +146,29 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
assertThat(age, is((DBObject) new BasicDBObject("v", 10)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-960
|
||||
*/
|
||||
@Test
|
||||
public void rendersAggregationOptionsInTypedAggregationContextCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(FooPerson.class);
|
||||
TypedAggregation<FooPerson> agg = newAggregation(FooPerson.class, project("name", "age")) //
|
||||
.withOptions(
|
||||
newAggregationOptions().allowDiskUse(true).explain(true).cursor(new BasicDBObject("foo", 1)).build());
|
||||
|
||||
DBObject dbo = agg.toDbObject("person", context);
|
||||
|
||||
DBObject projection = getPipelineElementFromAggregationAt(dbo, 0);
|
||||
assertThat(projection.containsField("$project"), is(true));
|
||||
|
||||
assertThat(projection.get("$project"), is((Object) new BasicDBObject("name", 1).append("age", 1)));
|
||||
|
||||
assertThat(dbo.get("allowDiskUse"), is((Object) true));
|
||||
assertThat(dbo.get("explain"), is((Object) true));
|
||||
assertThat(dbo.get("cursor"), is((Object) new BasicDBObject("foo", 1)));
|
||||
}
|
||||
|
||||
@Document(collection = "person")
|
||||
public static class FooPerson {
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
|
||||
import org.springframework.data.mongodb.core.mapping.TextScore;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -1807,6 +1808,32 @@ public class MappingMongoConverterUnitTests {
|
||||
assertThat(result.shape, is((Shape) sphere));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-976
|
||||
*/
|
||||
@Test
|
||||
public void shouldIgnoreTextScorePropertyWhenWriting() {
|
||||
|
||||
ClassWithTextScoreProperty source = new ClassWithTextScoreProperty();
|
||||
source.score = Float.MAX_VALUE;
|
||||
|
||||
BasicDBObject dbo = new BasicDBObject();
|
||||
converter.write(source, dbo);
|
||||
|
||||
assertThat(dbo.get("score"), nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-976
|
||||
*/
|
||||
@Test
|
||||
public void shouldIncludeTextScorePropertyWhenReading() {
|
||||
|
||||
ClassWithTextScoreProperty entity = converter
|
||||
.read(ClassWithTextScoreProperty.class, new BasicDBObject("score", 5F));
|
||||
assertThat(entity.score, equalTo(5F));
|
||||
}
|
||||
|
||||
static class GenericType<T> {
|
||||
T content;
|
||||
}
|
||||
@@ -2057,4 +2084,9 @@ public class MappingMongoConverterUnitTests {
|
||||
|
||||
Shape shape;
|
||||
}
|
||||
|
||||
class ClassWithTextScoreProperty {
|
||||
|
||||
@TextScore Float score;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -22,11 +22,14 @@ import java.util.List;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.util.Version;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -34,18 +37,17 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* Integration tests for {@link MongoPersistentEntityIndexCreator}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class MongoPersistentEntityIndexCreatorIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mongo1")
|
||||
MongoOperations templateOne;
|
||||
public static @ClassRule MongoVersionRule version = MongoVersionRule.atLeast(new Version(2, 6));
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mongo2")
|
||||
MongoOperations templateTwo;
|
||||
@Autowired @Qualifier("mongo1") MongoOperations templateOne;
|
||||
|
||||
@Autowired @Qualifier("mongo2") MongoOperations templateTwo;
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
|
||||
@@ -20,33 +20,45 @@ import static org.hamcrest.collection.IsEmptyCollection.*;
|
||||
import static org.hamcrest.core.IsEqual.*;
|
||||
import static org.hamcrest.core.IsInstanceOf.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.collection.IsEmptyCollection;
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mongodb.core.DBObjectTestUtils;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.CompoundIndexResolutionTests;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.GeoSpatialIndexResolutionTests;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.IndexResolutionTests;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.MixedIndexResolutionTests;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.TextIndexedResolutionTests;
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.Language;
|
||||
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.DBObject;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses({ IndexResolutionTests.class, GeoSpatialIndexResolutionTests.class, CompoundIndexResolutionTests.class,
|
||||
MixedIndexResolutionTests.class })
|
||||
TextIndexedResolutionTests.class, MixedIndexResolutionTests.class })
|
||||
public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
|
||||
/**
|
||||
@@ -289,9 +301,8 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class);
|
||||
|
||||
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||
assertThat(indexDefinition.getIndexOptions(),
|
||||
equalTo(new BasicDBObjectBuilder().add("name", "compound_index").add("unique", true).add("dropDups", true)
|
||||
.add("sparse", true).add("background", true).add("expireAfterSeconds", 10L).get()));
|
||||
assertThat(indexDefinition.getIndexOptions(), equalTo(new BasicDBObjectBuilder().add("name", "compound_index")
|
||||
.add("unique", true).add("dropDups", true).add("sparse", true).add("background", true).get()));
|
||||
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
|
||||
}
|
||||
|
||||
@@ -304,9 +315,8 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexDefinedOnSuperClass.class);
|
||||
|
||||
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||
assertThat(indexDefinition.getIndexOptions(),
|
||||
equalTo(new BasicDBObjectBuilder().add("name", "compound_index").add("unique", true).add("dropDups", true)
|
||||
.add("sparse", true).add("background", true).add("expireAfterSeconds", 10L).get()));
|
||||
assertThat(indexDefinition.getIndexOptions(), equalTo(new BasicDBObjectBuilder().add("name", "compound_index")
|
||||
.add("unique", true).add("dropDups", true).add("sparse", true).add("background", true).get()));
|
||||
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
|
||||
}
|
||||
|
||||
@@ -322,7 +332,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
assertThat(
|
||||
indexDefinition.getIndexOptions(),
|
||||
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()));
|
||||
}
|
||||
|
||||
@@ -364,6 +374,22 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
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")
|
||||
static class CompoundIndexOnLevelOne {
|
||||
|
||||
@@ -400,6 +426,168 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
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 TextIndexedResolutionTests {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldResolveSingleFieldTextIndexCorrectly() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnSinglePropertyInRoot.class);
|
||||
assertThat(indexDefinitions.size(), equalTo(1));
|
||||
assertIndexPathAndCollection("bar", "textIndexOnSinglePropertyInRoot", indexDefinitions.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldResolveMultiFieldTextIndexCorrectly() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnMutiplePropertiesInRoot.class);
|
||||
assertThat(indexDefinitions.size(), equalTo(1));
|
||||
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "textIndexOnMutiplePropertiesInRoot",
|
||||
indexDefinitions.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldResolveTextIndexOnElementCorrectly() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnNestedRoot.class);
|
||||
assertThat(indexDefinitions.size(), equalTo(1));
|
||||
assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedRoot", indexDefinitions.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldResolveTextIndexOnElementWithWeightCorrectly() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnNestedWithWeightRoot.class);
|
||||
assertThat(indexDefinitions.size(), equalTo(1));
|
||||
assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedWithWeightRoot",
|
||||
indexDefinitions.get(0));
|
||||
|
||||
DBObject weights = DBObjectTestUtils.getAsDBObject(indexDefinitions.get(0).getIndexOptions(), "weights");
|
||||
assertThat(weights.get("nested.foo"), IsEqual.<Object> equalTo(5F));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldResolveTextIndexOnElementWithMostSpecificWeightCorrectly() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnNestedWithMostSpecificValueRoot.class);
|
||||
assertThat(indexDefinitions.size(), equalTo(1));
|
||||
assertIndexPathAndCollection(new String[] { "nested.foo", "nested.bar" },
|
||||
"textIndexOnNestedWithMostSpecificValueRoot", indexDefinitions.get(0));
|
||||
|
||||
DBObject weights = DBObjectTestUtils.getAsDBObject(indexDefinitions.get(0).getIndexOptions(), "weights");
|
||||
assertThat(weights.get("nested.foo"), IsEqual.<Object> equalTo(5F));
|
||||
assertThat(weights.get("nested.bar"), IsEqual.<Object> equalTo(10F));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldSetDefaultLanguageCorrectly() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithDefaultLanguage.class);
|
||||
assertThat(indexDefinitions.get(0).getIndexOptions().get("default_language"), IsEqual.<Object> equalTo("spanish"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldResolveTextIndexLanguageOverrideCorrectly() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithLanguageOverrideOnNestedElementRoot.class);
|
||||
assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), IsEqual.<Object> equalTo("lang"));
|
||||
}
|
||||
|
||||
@Document
|
||||
static class TextIndexOnSinglePropertyInRoot {
|
||||
|
||||
String foo;
|
||||
|
||||
@TextIndexed String bar;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class TextIndexOnMutiplePropertiesInRoot {
|
||||
|
||||
@TextIndexed String foo;
|
||||
|
||||
@TextIndexed(weight = 5) String bar;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class TextIndexOnNestedRoot {
|
||||
|
||||
String bar;
|
||||
|
||||
@TextIndexed TextIndexOnNested nested;
|
||||
}
|
||||
|
||||
static class TextIndexOnNested {
|
||||
|
||||
String foo;
|
||||
|
||||
}
|
||||
|
||||
@Document
|
||||
static class TextIndexOnNestedWithWeightRoot {
|
||||
|
||||
@TextIndexed(weight = 5) TextIndexOnNested nested;
|
||||
|
||||
}
|
||||
|
||||
@Document
|
||||
static class TextIndexOnNestedWithMostSpecificValueRoot {
|
||||
@TextIndexed(weight = 5) TextIndexOnNestedWithMostSpecificValue nested;
|
||||
}
|
||||
|
||||
static class TextIndexOnNestedWithMostSpecificValue {
|
||||
|
||||
String foo;
|
||||
@TextIndexed(weight = 10) String bar;
|
||||
}
|
||||
|
||||
@Document(language = "spanish")
|
||||
static class DocumentWithDefaultLanguage {
|
||||
@TextIndexed String foo;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class DocumentWithLanguageOverrideOnNestedElementRoot {
|
||||
|
||||
DocumentWithLanguageOverrideOnNestedElement nested;
|
||||
}
|
||||
|
||||
static class DocumentWithLanguageOverrideOnNestedElement {
|
||||
|
||||
@TextIndexed String foo;
|
||||
|
||||
@Language String lang;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class MixedIndexResolutionTests {
|
||||
@@ -469,9 +657,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class);
|
||||
assertIndexPathAndCollection("reference.indexedProperty", "cycleOnLevelOne", indexDefinitions.get(0));
|
||||
assertIndexPathAndCollection("reference.cyclicReference.reference.indexedProperty", "cycleOnLevelOne",
|
||||
indexDefinitions.get(1));
|
||||
assertThat(indexDefinitions, hasSize(2));
|
||||
assertThat(indexDefinitions, hasSize(1));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,6 +674,76 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String language() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
MongoPersistentProperty propertyMock = mock(MongoPersistentProperty.class);
|
||||
when(propertyMock.isEntity()).thenReturn(true);
|
||||
when(propertyMock.getOwner()).thenReturn(
|
||||
(PersistentEntity) MongoPersistentEntityDummyBuilder.forClass(Object.class).build());
|
||||
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);
|
||||
}
|
||||
|
||||
@Document
|
||||
static class MixedIndexRoot {
|
||||
|
||||
@@ -554,6 +810,32 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
|
||||
@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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {
|
||||
@@ -586,4 +868,5 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
}
|
||||
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,96 @@
|
||||
/*
|
||||
* 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.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.config.AbstractIntegrationTests;
|
||||
import org.springframework.data.mongodb.core.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Language;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.util.Version;
|
||||
|
||||
import com.mongodb.WriteConcern;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TextIndexTests extends AbstractIntegrationTests {
|
||||
|
||||
public static @ClassRule MongoVersionRule version = MongoVersionRule.atLeast(new Version(2, 6));
|
||||
|
||||
private @Autowired MongoTemplate template;
|
||||
private IndexOperations indexOps;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
template.setWriteConcern(WriteConcern.FSYNC_SAFE);
|
||||
this.indexOps = template.indexOps(TextIndexedDocumentRoot.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void indexInfoShouldHaveBeenCreatedCorrectly() {
|
||||
|
||||
List<IndexInfo> indexInfos = indexOps.getIndexInfo();
|
||||
|
||||
assertThat(indexInfos.size(), is(2));
|
||||
|
||||
List<IndexField> fields = indexInfos.get(0).getIndexFields();
|
||||
assertThat(fields.size(), is(1));
|
||||
assertThat(fields, hasItem(IndexField.create("_id", Direction.ASC)));
|
||||
|
||||
IndexInfo textIndexInfo = indexInfos.get(1);
|
||||
List<IndexField> textIndexFields = textIndexInfo.getIndexFields();
|
||||
assertThat(textIndexFields.size(), is(4));
|
||||
assertThat(textIndexFields, hasItem(IndexField.text("textIndexedPropertyWithDefaultWeight", 1F)));
|
||||
assertThat(textIndexFields, hasItem(IndexField.text("textIndexedPropertyWithWeight", 5F)));
|
||||
assertThat(textIndexFields, hasItem(IndexField.text("nestedDocument.textIndexedPropertyInNestedDocument", 1F)));
|
||||
assertThat(textIndexFields, hasItem(IndexField.create("_ftsx", Direction.ASC)));
|
||||
assertThat(textIndexInfo.getLanguage(), is("spanish"));
|
||||
}
|
||||
|
||||
@Document(language = "spanish")
|
||||
static class TextIndexedDocumentRoot {
|
||||
|
||||
@TextIndexed String textIndexedPropertyWithDefaultWeight;
|
||||
@TextIndexed(weight = 5) String textIndexedPropertyWithWeight;
|
||||
|
||||
TextIndexedDocumentWihtLanguageOverride nestedDocument;
|
||||
}
|
||||
|
||||
static class TextIndexedDocumentWihtLanguageOverride {
|
||||
|
||||
@Language String lang;
|
||||
|
||||
@TextIndexed String textIndexedPropertyInNestedDocument;
|
||||
|
||||
String nonTextIndexedProperty;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2013 by the original author(s).
|
||||
* Copyright 2011-2014 by the original author(s).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,12 +30,12 @@ import org.springframework.data.util.ClassTypeInformation;
|
||||
* Unit tests for {@link BasicMongoPersistentEntity}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BasicMongoPersistentEntityUnitTests {
|
||||
|
||||
@Mock
|
||||
ApplicationContext context;
|
||||
@Mock ApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void subclassInheritsAtDocumentAnnotation() {
|
||||
@@ -69,6 +69,17 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
assertThat(entity.getCollection(), is("reference"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldDetectLanguageCorrectly() {
|
||||
|
||||
BasicMongoPersistentEntity<DocumentWithLanguage> entity = new BasicMongoPersistentEntity<DocumentWithLanguage>(
|
||||
ClassTypeInformation.from(DocumentWithLanguage.class));
|
||||
assertThat(entity.getLanguage(), is("spanish"));
|
||||
}
|
||||
|
||||
@Document(collection = "contacts")
|
||||
class Contact {
|
||||
|
||||
@@ -95,4 +106,9 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
return collectionName;
|
||||
}
|
||||
}
|
||||
|
||||
@Document(language = "spanish")
|
||||
static class DocumentWithLanguage {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2013 by the original author(s).
|
||||
* Copyright 2011-2014 by the original author(s).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,7 +26,10 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -35,13 +38,13 @@ import org.springframework.util.ReflectionUtils;
|
||||
* Unit test for {@link BasicMongoPersistentProperty}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class BasicMongoPersistentPropertyUnitTests {
|
||||
|
||||
MongoPersistentEntity<Person> entity;
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
@Rule public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -121,29 +124,86 @@ public class BasicMongoPersistentPropertyUnitTests {
|
||||
property.getFieldName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldDetectAnnotatedLanguagePropertyCorrectly() {
|
||||
|
||||
BasicMongoPersistentEntity<DocumentWithLanguageProperty> persistentEntity = new BasicMongoPersistentEntity<DocumentWithLanguageProperty>(
|
||||
ClassTypeInformation.from(DocumentWithLanguageProperty.class));
|
||||
|
||||
MongoPersistentProperty property = getPropertyFor(persistentEntity, "lang");
|
||||
assertThat(property.isLanguageProperty(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-937
|
||||
*/
|
||||
@Test
|
||||
public void shouldDetectIplicitLanguagePropertyCorrectly() {
|
||||
|
||||
BasicMongoPersistentEntity<DocumentWithImplicitLanguageProperty> persistentEntity = new BasicMongoPersistentEntity<DocumentWithImplicitLanguageProperty>(
|
||||
ClassTypeInformation.from(DocumentWithImplicitLanguageProperty.class));
|
||||
|
||||
MongoPersistentProperty property = getPropertyFor(persistentEntity, "language");
|
||||
assertThat(property.isLanguageProperty(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-976
|
||||
*/
|
||||
@Test
|
||||
public void shouldDetectTextScorePropertyCorrectly() {
|
||||
|
||||
BasicMongoPersistentEntity<DocumentWithTextScoreProperty> persistentEntity = new BasicMongoPersistentEntity<DocumentWithTextScoreProperty>(
|
||||
ClassTypeInformation.from(DocumentWithTextScoreProperty.class));
|
||||
|
||||
MongoPersistentProperty property = getPropertyFor(persistentEntity, "score");
|
||||
assertThat(property.isTextScoreProperty(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-976
|
||||
*/
|
||||
@Test
|
||||
public void shouldDetectTextScoreAsReadOnlyProperty() {
|
||||
|
||||
BasicMongoPersistentEntity<DocumentWithTextScoreProperty> persistentEntity = new BasicMongoPersistentEntity<DocumentWithTextScoreProperty>(
|
||||
ClassTypeInformation.from(DocumentWithTextScoreProperty.class));
|
||||
|
||||
MongoPersistentProperty property = getPropertyFor(persistentEntity, "score");
|
||||
assertThat(property.isWritable(), is(false));
|
||||
}
|
||||
|
||||
private MongoPersistentProperty getPropertyFor(Field field) {
|
||||
return new BasicMongoPersistentProperty(field, null, entity, new SimpleTypeHolder(),
|
||||
return getPropertyFor(entity, field);
|
||||
}
|
||||
|
||||
private MongoPersistentProperty getPropertyFor(MongoPersistentEntity<?> persistentEntity, String fieldname) {
|
||||
return getPropertyFor(persistentEntity, ReflectionUtils.findField(persistentEntity.getType(), fieldname));
|
||||
}
|
||||
|
||||
private MongoPersistentProperty getPropertyFor(MongoPersistentEntity<?> persistentEntity, Field field) {
|
||||
return new BasicMongoPersistentProperty(field, null, persistentEntity, new SimpleTypeHolder(),
|
||||
PropertyNameFieldNamingStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
class Person {
|
||||
|
||||
@Id
|
||||
String id;
|
||||
@Id String id;
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Field("foo")
|
||||
String firstname;
|
||||
@org.springframework.data.mongodb.core.mapping.Field("foo") String firstname;
|
||||
String lastname;
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Field(order = -20)
|
||||
String ssn;
|
||||
@org.springframework.data.mongodb.core.mapping.Field(order = -20) String ssn;
|
||||
}
|
||||
|
||||
enum UppercaseFieldNamingStrategy implements FieldNamingStrategy {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public String getFieldName(MongoPersistentProperty property) {
|
||||
public String getFieldName(PersistentProperty<?> property) {
|
||||
return property.getName().toUpperCase(Locale.US);
|
||||
}
|
||||
}
|
||||
@@ -152,8 +212,22 @@ public class BasicMongoPersistentPropertyUnitTests {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public String getFieldName(MongoPersistentProperty property) {
|
||||
public String getFieldName(PersistentProperty<?> property) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class DocumentWithLanguageProperty {
|
||||
|
||||
@Language String lang;
|
||||
}
|
||||
|
||||
static class DocumentWithImplicitLanguageProperty {
|
||||
|
||||
String language;
|
||||
}
|
||||
|
||||
static class DocumentWithTextScoreProperty {
|
||||
@TextScore Float score;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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 static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CamelCaseAbbreviatingFieldNamingStrategy}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CamelCaseAbbreviatingFieldNamingStrategyUnitTests {
|
||||
|
||||
FieldNamingStrategy strategy = new CamelCaseAbbreviatingFieldNamingStrategy();
|
||||
|
||||
@Mock
|
||||
MongoPersistentProperty property;
|
||||
|
||||
@Test
|
||||
public void foo() {
|
||||
assertFieldNameForPropertyName("fooBar", "fb");
|
||||
assertFieldNameForPropertyName("fooBARFooBar", "fbfb");
|
||||
}
|
||||
|
||||
private void assertFieldNameForPropertyName(String propertyName, String fieldName) {
|
||||
|
||||
when(property.getName()).thenReturn(propertyName);
|
||||
assertThat(strategy.getFieldName(property), is(fieldName));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2013 by the original author(s).
|
||||
* Copyright 2011-2014 by the original author(s).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,8 @@ import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
@@ -40,6 +42,7 @@ import com.mongodb.DBRef;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MongoMappingContextUnitTests {
|
||||
@@ -84,7 +87,7 @@ public class MongoMappingContextUnitTests {
|
||||
context.setApplicationContext(applicationContext);
|
||||
context.setFieldNamingStrategy(new FieldNamingStrategy() {
|
||||
|
||||
public String getFieldName(MongoPersistentProperty property) {
|
||||
public String getFieldName(PersistentProperty<?> property) {
|
||||
return property.getName().toUpperCase(Locale.US);
|
||||
}
|
||||
});
|
||||
@@ -177,6 +180,21 @@ public class MongoMappingContextUnitTests {
|
||||
context.getPersistentEntity(ClassWithMultipleImplicitIds.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-976
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectClassWithInvalidTextScoreProperty() {
|
||||
|
||||
exception.expect(MappingException.class);
|
||||
exception.expectMessage("score");
|
||||
exception.expectMessage("Float");
|
||||
exception.expectMessage("Double");
|
||||
|
||||
MongoMappingContext context = new MongoMappingContext();
|
||||
context.getPersistentEntity(ClassWithInvalidTextScoreProperty.class);
|
||||
}
|
||||
|
||||
public class SampleClass {
|
||||
|
||||
Map<String, SampleClass> children;
|
||||
@@ -238,4 +256,9 @@ public class MongoMappingContextUnitTests {
|
||||
String _id;
|
||||
String id;
|
||||
}
|
||||
|
||||
class ClassWithInvalidTextScoreProperty {
|
||||
|
||||
@TextScore Locale score;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguage() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-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 static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SnakeCaseFieldNamingStrategy}.
|
||||
*
|
||||
* @author Ryan Tenney
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SnakeCaseFieldNamingStrategyUnitTests {
|
||||
|
||||
FieldNamingStrategy strategy = new SnakeCaseFieldNamingStrategy();
|
||||
|
||||
@Mock MongoPersistentProperty property;
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-866
|
||||
*/
|
||||
@Test
|
||||
public void rendersSnakeCaseFieldNames() {
|
||||
|
||||
assertFieldNameForPropertyName("fooBar", "foo_bar");
|
||||
assertFieldNameForPropertyName("FooBar", "foo_bar");
|
||||
assertFieldNameForPropertyName("foo_bar", "foo_bar");
|
||||
assertFieldNameForPropertyName("FOO_BAR", "foo_bar");
|
||||
}
|
||||
|
||||
private void assertFieldNameForPropertyName(String propertyName, String fieldName) {
|
||||
|
||||
when(property.getName()).thenReturn(propertyName);
|
||||
assertThat(strategy.getFieldName(property), is(fieldName));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -20,10 +20,13 @@ import static org.junit.Assert.*;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.util.Version;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -33,13 +36,15 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* @see DATAMONGO-36
|
||||
* @author Maciej Walkowiak
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class ValidatingMongoEventListenerTests {
|
||||
|
||||
@Autowired
|
||||
MongoTemplate mongoTemplate;
|
||||
public static @ClassRule MongoVersionRule version = MongoVersionRule.atLeast(new Version(2, 6));
|
||||
|
||||
@Autowired MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void shouldThrowConstraintViolationException() {
|
||||
|
||||
@@ -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");
|
||||
* 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.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.geo.Box;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
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
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@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) {
|
||||
createMapReduceData();
|
||||
MapReduceResults<ValueObject> results;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.query;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* A {@link TypeSafeMatcher} that tests whether a given {@link Query} matches a query specification.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @param <T>
|
||||
*/
|
||||
public class IsQuery<T extends Query> extends TypeSafeMatcher<T> {
|
||||
|
||||
protected DBObject query;
|
||||
protected DBObject sort;
|
||||
protected DBObject fields;
|
||||
|
||||
private int skip;
|
||||
private int limit;
|
||||
private String hint;
|
||||
|
||||
protected IsQuery() {
|
||||
query = new BasicDBObject();
|
||||
sort = new BasicDBObject();
|
||||
}
|
||||
|
||||
public static <T extends BasicQuery> IsQuery<T> isQuery() {
|
||||
return new IsQuery<T>();
|
||||
}
|
||||
|
||||
public IsQuery<T> limitingTo(int limit) {
|
||||
this.limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsQuery<T> skippig(int skip) {
|
||||
this.skip = skip;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsQuery<T> providingHint(String hint) {
|
||||
this.hint = hint;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsQuery<T> includingField(String fieldname) {
|
||||
|
||||
if (fields == null) {
|
||||
fields = new BasicDBObject();
|
||||
}
|
||||
fields.put(fieldname, 1);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsQuery<T> excludingField(String fieldname) {
|
||||
|
||||
if (fields == null) {
|
||||
fields = new BasicDBObject();
|
||||
}
|
||||
fields.put(fieldname, -1);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsQuery<T> sortingBy(String fieldname, Direction direction) {
|
||||
|
||||
sort.put(fieldname, Direction.ASC.equals(direction) ? 1 : -1);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
|
||||
BasicQuery expected = new BasicQuery(this.query, this.fields);
|
||||
expected.setSortObject(sort);
|
||||
expected.skip(this.skip);
|
||||
expected.limit(this.limit);
|
||||
|
||||
if (StringUtils.hasText(this.hint)) {
|
||||
expected.withHint(this.hint);
|
||||
}
|
||||
|
||||
description.appendValue(expected);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(T item) {
|
||||
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new IsEqual<DBObject>(query).matches(item.getQueryObject())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new IsEqual<DBObject>(sort).matches(item.getSortObject())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new IsEqual<DBObject>(fields).matches(item.getFieldsObject())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new IsEqual<String>(this.hint).matches(item.getHint())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new IsEqual<Integer>(this.skip).matches(item.getSkip())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new IsEqual<Integer>(this.limit).matches(item.getLimit())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,9 @@ import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.BasicDBObjectBuilder;
|
||||
|
||||
/**
|
||||
* Test cases for {@link Update}.
|
||||
*
|
||||
@@ -30,6 +33,7 @@ import org.junit.Test;
|
||||
* @author Thomas Risberg
|
||||
* @author Becca Gaspard
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class UpdateTests {
|
||||
|
||||
@@ -284,4 +288,125 @@ public class UpdateTests {
|
||||
public void testCreatingUpdateWithNullKeyThrowsException() {
|
||||
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-944
|
||||
*/
|
||||
@Test
|
||||
public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsingDate() {
|
||||
|
||||
Update update = new Update().currentDate("foo");
|
||||
assertThat(update.getUpdateObject(),
|
||||
equalTo(new BasicDBObjectBuilder().add("$currentDate", new BasicDBObject("foo", true)).get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-944
|
||||
*/
|
||||
@Test
|
||||
public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhenUsingDate() {
|
||||
|
||||
Update update = new Update().currentDate("foo").currentDate("bar");
|
||||
assertThat(update.getUpdateObject(),
|
||||
equalTo(new BasicDBObjectBuilder().add("$currentDate", new BasicDBObject("foo", true).append("bar", true))
|
||||
.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-944
|
||||
*/
|
||||
@Test
|
||||
public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsingTimestamp() {
|
||||
|
||||
Update update = new Update().currentTimestamp("foo");
|
||||
assertThat(
|
||||
update.getUpdateObject(),
|
||||
equalTo(new BasicDBObjectBuilder().add("$currentDate",
|
||||
new BasicDBObject("foo", new BasicDBObject("$type", "timestamp"))).get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-944
|
||||
*/
|
||||
@Test
|
||||
public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhenUsingTimestamp() {
|
||||
|
||||
Update update = new Update().currentTimestamp("foo").currentTimestamp("bar");
|
||||
assertThat(
|
||||
update.getUpdateObject(),
|
||||
equalTo(new BasicDBObjectBuilder().add(
|
||||
"$currentDate",
|
||||
new BasicDBObject("foo", new BasicDBObject("$type", "timestamp")).append("bar", new BasicDBObject("$type",
|
||||
"timestamp"))).get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-944
|
||||
*/
|
||||
@Test
|
||||
public void getUpdateObjectShouldReturnCurrentDateCorrectlyWhenUsingMixedDateAndTimestamp() {
|
||||
|
||||
Update update = new Update().currentDate("foo").currentTimestamp("bar");
|
||||
assertThat(
|
||||
update.getUpdateObject(),
|
||||
equalTo(new BasicDBObjectBuilder().add("$currentDate",
|
||||
new BasicDBObject("foo", true).append("bar", new BasicDBObject("$type", "timestamp"))).get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.query.text;
|
||||
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.springframework.data.mongodb.core.query.IsQuery;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* A {@link TypeSafeMatcher} that tests whether a given {@link TextQuery} matches a query specification.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @param <T>
|
||||
*/
|
||||
public class IsTextQuery<T extends TextQuery> extends IsQuery<T> {
|
||||
|
||||
private final String SCORE_DEFAULT_FIELDNAME = "score";
|
||||
private final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore");
|
||||
|
||||
private String scoreFieldName = SCORE_DEFAULT_FIELDNAME;
|
||||
|
||||
private IsTextQuery() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static <T extends TextQuery> IsTextQuery<T> isTextQuery() {
|
||||
return new IsTextQuery<T>();
|
||||
}
|
||||
|
||||
public IsTextQuery<T> searchingFor(String term) {
|
||||
appendTerm(term);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsTextQuery<T> inLanguage(String language) {
|
||||
appendLanguage(language);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsTextQuery<T> returningScore() {
|
||||
|
||||
if (fields == null) {
|
||||
fields = new BasicDBObject();
|
||||
}
|
||||
fields.put(scoreFieldName, META_TEXT_SCORE);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IsTextQuery<T> returningScoreAs(String fieldname) {
|
||||
|
||||
this.scoreFieldName = fieldname != null ? fieldname : SCORE_DEFAULT_FIELDNAME;
|
||||
|
||||
return this.returningScore();
|
||||
}
|
||||
|
||||
public IsTextQuery<T> sortingByScore() {
|
||||
|
||||
sort.put(scoreFieldName, META_TEXT_SCORE);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void appendLanguage(String language) {
|
||||
|
||||
DBObject dbo = getOrCreateTextDbo();
|
||||
dbo.put("$language", language);
|
||||
}
|
||||
|
||||
private DBObject getOrCreateTextDbo() {
|
||||
|
||||
DBObject dbo = (DBObject) query.get("$text");
|
||||
if (dbo == null) {
|
||||
dbo = new BasicDBObject();
|
||||
}
|
||||
|
||||
return dbo;
|
||||
}
|
||||
|
||||
private void appendTerm(String term) {
|
||||
|
||||
DBObject dbo = getOrCreateTextDbo();
|
||||
String searchString = (String) dbo.get("$search");
|
||||
if (StringUtils.hasText(searchString)) {
|
||||
searchString += (" " + term);
|
||||
} else {
|
||||
searchString = term;
|
||||
}
|
||||
dbo.put("$search", searchString);
|
||||
query.put("$text", dbo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.query.text;
|
||||
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.DBObjectTestUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.util.JSON;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TextCriteriaUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotHaveLanguageField() {
|
||||
|
||||
TextCriteria criteria = TextCriteria.forDefaultLanguage();
|
||||
Assert.assertThat(criteria.getCriteriaObject(), IsEqual.equalTo(searchObject("{ }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotHaveLanguageForNonDefaultLanguageField() {
|
||||
|
||||
TextCriteria criteria = TextCriteria.forLanguage("spanish");
|
||||
Assert.assertThat(criteria.getCriteriaObject(), IsEqual.equalTo(searchObject("{ \"$language\" : \"spanish\" }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateSearchFieldForSingleTermCorrectly() {
|
||||
|
||||
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("cake");
|
||||
Assert.assertThat(criteria.getCriteriaObject(), IsEqual.equalTo(searchObject("{ \"$search\" : \"cake\" }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateSearchFieldCorrectlyForMultipleTermsCorrectly() {
|
||||
|
||||
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("bake", "coffee", "cake");
|
||||
Assert.assertThat(criteria.getCriteriaObject(),
|
||||
IsEqual.equalTo(searchObject("{ \"$search\" : \"bake coffee cake\" }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateSearchFieldForPhraseCorrectly() {
|
||||
|
||||
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingPhrase("coffee cake");
|
||||
Assert.assertThat(DBObjectTestUtils.getAsDBObject(criteria.getCriteriaObject(), "$text"),
|
||||
IsEqual.<DBObject> equalTo(new BasicDBObject("$search", "\"coffee cake\"")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateNotFieldCorrectly() {
|
||||
|
||||
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatching("cake");
|
||||
Assert.assertThat(criteria.getCriteriaObject(), IsEqual.equalTo(searchObject("{ \"$search\" : \"-cake\" }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateSearchFieldCorrectlyForNotMultipleTermsCorrectly() {
|
||||
|
||||
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingAny("bake", "coffee", "cake");
|
||||
Assert.assertThat(criteria.getCriteriaObject(),
|
||||
IsEqual.equalTo(searchObject("{ \"$search\" : \"-bake -coffee -cake\" }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateSearchFieldForNotPhraseCorrectly() {
|
||||
|
||||
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingPhrase("coffee cake");
|
||||
Assert.assertThat(DBObjectTestUtils.getAsDBObject(criteria.getCriteriaObject(), "$text"),
|
||||
IsEqual.<DBObject> equalTo(new BasicDBObject("$search", "-\"coffee cake\"")));
|
||||
}
|
||||
|
||||
private DBObject searchObject(String json) {
|
||||
return new BasicDBObject("$text", JSON.parse(json));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* 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.query.text;
|
||||
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.*;
|
||||
import static org.hamcrest.collection.IsEmptyCollection.*;
|
||||
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
|
||||
import static org.hamcrest.core.AnyOf.*;
|
||||
import static org.hamcrest.core.IsCollectionContaining.*;
|
||||
import static org.hamcrest.core.IsEqual.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.mongodb.config.AbstractIntegrationTests;
|
||||
import org.springframework.data.mongodb.core.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.Language;
|
||||
import org.springframework.data.mongodb.core.mapping.TextScore;
|
||||
import org.springframework.data.mongodb.core.query.text.TextQueryTests.FullTextDoc.FullTextDocBuilder;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.util.Version;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TextQueryTests extends AbstractIntegrationTests {
|
||||
|
||||
public static @ClassRule MongoVersionRule version = MongoVersionRule.atLeast(new Version(2, 6));
|
||||
|
||||
private static final FullTextDoc BAKE = new FullTextDocBuilder().headline("bake").build();
|
||||
private static final FullTextDoc COFFEE = new FullTextDocBuilder().subHeadline("coffee").build();
|
||||
private static final FullTextDoc CAKE = new FullTextDocBuilder().body("cake").build();
|
||||
private static final FullTextDoc NOT_TO_BE_FOUND = new FullTextDocBuilder().headline("o_O").build();
|
||||
private static final FullTextDoc SPANISH_MILK = new FullTextDocBuilder().headline("leche").lanugage("spanish")
|
||||
.build();
|
||||
private static final FullTextDoc FRENCH_MILK = new FullTextDocBuilder().headline("leche").lanugage("french").build();
|
||||
private static final FullTextDoc MILK_AND_SUGAR = new FullTextDocBuilder().headline("milk and sugar").build();
|
||||
|
||||
private @Autowired MongoOperations template;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
IndexOperations indexOps = template.indexOps(FullTextDoc.class);
|
||||
indexOps.dropAllIndexes();
|
||||
|
||||
indexOps.ensureIndex(new IndexDefinition() {
|
||||
|
||||
@Override
|
||||
public DBObject getIndexOptions() {
|
||||
DBObject options = new BasicDBObject();
|
||||
options.put("weights", weights());
|
||||
options.put("name", "TextQueryTests_TextIndex");
|
||||
options.put("language_override", "lang");
|
||||
options.put("default_language", "english");
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBObject getIndexKeys() {
|
||||
DBObject keys = new BasicDBObject();
|
||||
keys.put("headline", "text");
|
||||
keys.put("subheadline", "text");
|
||||
keys.put("body", "text");
|
||||
return keys;
|
||||
}
|
||||
|
||||
private DBObject weights() {
|
||||
DBObject weights = new BasicDBObject();
|
||||
weights.put("headline", 10);
|
||||
weights.put("subheadline", 5);
|
||||
weights.put("body", 1);
|
||||
return weights;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldOnlyFindDocumentsMatchingAnyWordOfGivenQuery() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
|
||||
List<FullTextDoc> result = template.find(new TextQuery("bake coffee cake"), FullTextDoc.class);
|
||||
assertThat(result, hasSize(3));
|
||||
assertThat(result, hasItems(BAKE, COFFEE, CAKE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotFindDocumentsWhenQueryDoesNotMatchAnyDocumentInIndex() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
|
||||
List<FullTextDoc> result = template.find(new TextQuery("tasmanian devil"), FullTextDoc.class);
|
||||
assertThat(result, hasSize(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldApplySortByScoreCorrectly() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
FullTextDoc coffee2 = new FullTextDocBuilder().headline("coffee").build();
|
||||
template.insert(coffee2);
|
||||
|
||||
List<FullTextDoc> result = template.find(new TextQuery("bake coffee cake").sortByScore(), FullTextDoc.class);
|
||||
assertThat(result, hasSize(4));
|
||||
assertThat(result.get(0), anyOf(equalTo(BAKE), equalTo(coffee2)));
|
||||
assertThat(result.get(1), anyOf(equalTo(BAKE), equalTo(coffee2)));
|
||||
assertThat(result.get(2), equalTo(COFFEE));
|
||||
assertThat(result.get(3), equalTo(CAKE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldFindTextInAnyLanguage() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
List<FullTextDoc> result = template.find(new TextQuery("leche"), FullTextDoc.class);
|
||||
assertThat(result, hasSize(2));
|
||||
assertThat(result, hasItems(SPANISH_MILK, FRENCH_MILK));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldOnlyFindTextInSpecificLanguage() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
List<FullTextDoc> result = template.find(new TextQuery("leche").addCriteria(where("language").is("spanish")),
|
||||
FullTextDoc.class);
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result.get(0), equalTo(SPANISH_MILK));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotFindDocumentsWithNegatedTerms() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
|
||||
List<FullTextDoc> result = template.find(new TextQuery("bake coffee -cake"), FullTextDoc.class);
|
||||
assertThat(result, hasSize(2));
|
||||
assertThat(result, hasItems(BAKE, COFFEE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-976
|
||||
*/
|
||||
@Test
|
||||
public void shouldInlcudeScoreCorreclty() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
|
||||
List<FullTextDoc> result = template.find(new TextQuery("bake coffee -cake").includeScore().sortByScore(),
|
||||
FullTextDoc.class);
|
||||
|
||||
assertThat(result, hasSize(2));
|
||||
for (FullTextDoc scoredDoc : result) {
|
||||
assertTrue(scoredDoc.score > 0F);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldApplyPhraseCorrectly() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
|
||||
TextQuery query = TextQuery.queryText(TextCriteria.forDefaultLanguage().matchingPhrase("milk and sugar"));
|
||||
List<FullTextDoc> result = template.find(query, FullTextDoc.class);
|
||||
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result, contains(MILK_AND_SUGAR));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldReturnEmptyListWhenNoDocumentsMatchGivenPhrase() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
|
||||
TextQuery query = TextQuery.queryText(TextCriteria.forDefaultLanguage().matchingPhrase("milk no sugar"));
|
||||
List<FullTextDoc> result = template.find(query, FullTextDoc.class);
|
||||
|
||||
assertThat(result, empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldApplyPaginationCorrectly() {
|
||||
|
||||
initWithDefaultDocuments();
|
||||
|
||||
// page 1
|
||||
List<FullTextDoc> result = template.find(new TextQuery("bake coffee cake").sortByScore()
|
||||
.with(new PageRequest(0, 2)), FullTextDoc.class);
|
||||
assertThat(result, hasSize(2));
|
||||
assertThat(result, contains(BAKE, COFFEE));
|
||||
|
||||
// page 2
|
||||
result = template.find(new TextQuery("bake coffee cake").sortByScore().with(new PageRequest(1, 2)),
|
||||
FullTextDoc.class);
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result, contains(CAKE));
|
||||
}
|
||||
|
||||
private void initWithDefaultDocuments() {
|
||||
this.template.save(BAKE);
|
||||
this.template.save(COFFEE);
|
||||
this.template.save(CAKE);
|
||||
this.template.save(NOT_TO_BE_FOUND);
|
||||
this.template.save(SPANISH_MILK);
|
||||
this.template.save(FRENCH_MILK);
|
||||
this.template.save(MILK_AND_SUGAR);
|
||||
}
|
||||
|
||||
@Document(collection = "fullTextDoc")
|
||||
static class FullTextDoc {
|
||||
|
||||
@Id String id;
|
||||
|
||||
private @Language @Field("lang") String language;
|
||||
|
||||
private String headline;
|
||||
private String subheadline;
|
||||
private String body;
|
||||
|
||||
private @TextScore Float score;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((body == null) ? 0 : body.hashCode());
|
||||
result = prime * result + ((headline == null) ? 0 : headline.hashCode());
|
||||
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||
result = prime * result + ((language == null) ? 0 : language.hashCode());
|
||||
result = prime * result + ((subheadline == null) ? 0 : subheadline.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof FullTextDoc)) {
|
||||
return false;
|
||||
}
|
||||
FullTextDoc other = (FullTextDoc) obj;
|
||||
if (body == null) {
|
||||
if (other.body != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!body.equals(other.body)) {
|
||||
return false;
|
||||
}
|
||||
if (headline == null) {
|
||||
if (other.headline != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!headline.equals(other.headline)) {
|
||||
return false;
|
||||
}
|
||||
if (id == null) {
|
||||
if (other.id != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!id.equals(other.id)) {
|
||||
return false;
|
||||
}
|
||||
if (language == null) {
|
||||
if (other.language != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!language.equals(other.language)) {
|
||||
return false;
|
||||
}
|
||||
if (subheadline == null) {
|
||||
if (other.subheadline != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!subheadline.equals(other.subheadline)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static class FullTextDocBuilder {
|
||||
|
||||
private FullTextDoc instance;
|
||||
|
||||
public FullTextDocBuilder() {
|
||||
this.instance = new FullTextDoc();
|
||||
}
|
||||
|
||||
public FullTextDocBuilder headline(String headline) {
|
||||
this.instance.headline = headline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FullTextDocBuilder subHeadline(String subHeadline) {
|
||||
this.instance.subheadline = subHeadline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FullTextDocBuilder body(String body) {
|
||||
this.instance.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FullTextDocBuilder lanugage(String language) {
|
||||
this.instance.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FullTextDoc build() {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.query.text;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.query.text.IsTextQuery.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TextQueryUnitTests {
|
||||
|
||||
private static final String QUERY = "bake coffee cake";
|
||||
private static final String LANGUAGE_SPANISH = "spanish";
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldCreateQueryObjectCorrectly() {
|
||||
assertThat(new TextQuery(QUERY), isTextQuery().searchingFor(QUERY));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldIncludeLanguageInQueryObjectWhenNotNull() {
|
||||
assertThat(new TextQuery(QUERY, LANGUAGE_SPANISH), isTextQuery().searchingFor(QUERY).inLanguage(LANGUAGE_SPANISH));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldIncludeScoreFieldCorrectly() {
|
||||
assertThat(new TextQuery(QUERY).includeScore(), isTextQuery().searchingFor(QUERY).returningScore());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotOverrideExistingProjections() {
|
||||
|
||||
TextQuery query = new TextQuery(TextCriteria.forDefaultLanguage().matching(QUERY)).includeScore();
|
||||
query.fields().include("foo");
|
||||
|
||||
assertThat(query, isTextQuery().searchingFor(QUERY).returningScore().includingField("foo"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldIncludeSortingByScoreCorrectly() {
|
||||
assertThat(new TextQuery(QUERY).sortByScore(), isTextQuery().searchingFor(QUERY).returningScore().sortingByScore());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotOverrideExistingSort() {
|
||||
|
||||
TextQuery query = new TextQuery(QUERY);
|
||||
query.with(new Sort(Direction.DESC, "foo"));
|
||||
query.sortByScore();
|
||||
|
||||
assertThat(query,
|
||||
isTextQuery().searchingFor(QUERY).returningScore().sortingByScore().sortingBy("foo", Direction.DESC));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-850
|
||||
*/
|
||||
@Test
|
||||
public void shouldUseCustomFieldnameForScoring() {
|
||||
TextQuery query = new TextQuery(QUERY).includeScore("customFieldForScore").sortByScore();
|
||||
|
||||
assertThat(query, isTextQuery().searchingFor(QUERY).returningScoreAs("customFieldForScore").sortingByScore());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -880,4 +881,121 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
|
||||
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));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-950
|
||||
*/
|
||||
@Test
|
||||
public void shouldLimitCollectionQueryToMaxResultsWhenPresent() {
|
||||
|
||||
repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), new Person("Bob-3",
|
||||
"Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan")));
|
||||
List<Person> result = repository.findTop3ByLastnameStartingWith("Dylan");
|
||||
assertThat(result.size(), is(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-950
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotLimitPagedQueryWhenPageRequestWithinBounds() {
|
||||
|
||||
repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), new Person("Bob-3",
|
||||
"Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan")));
|
||||
Page<Person> result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(0, 2));
|
||||
assertThat(result.getContent().size(), is(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-950
|
||||
*/
|
||||
@Test
|
||||
public void shouldLimitPagedQueryWhenPageRequestExceedsUpperBoundary() {
|
||||
|
||||
repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), new Person("Bob-3",
|
||||
"Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan")));
|
||||
Page<Person> result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(1, 2));
|
||||
assertThat(result.getContent().size(), is(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-950
|
||||
*/
|
||||
@Test
|
||||
public void shouldReturnEmptyWhenPageRequestedPageIsTotallyOutOfScopeForLimit() {
|
||||
|
||||
repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), new Person("Bob-3",
|
||||
"Dylan"), new Person("Bob-4", "Dylan"), new Person("Bob-5", "Dylan")));
|
||||
Page<Person> result = repository.findTop3ByLastnameStartingWith("Dylan", new PageRequest(2, 2));
|
||||
assertThat(result.getContent().size(), is(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +261,16 @@ public class Person extends Contact {
|
||||
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)
|
||||
*
|
||||
|
||||
@@ -290,4 +290,27 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
* @see DATAMONGO-893
|
||||
*/
|
||||
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-950
|
||||
*/
|
||||
List<Person> findTop3ByLastnameStartingWith(String lastname);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-950
|
||||
*/
|
||||
Page<Person> findTop3ByLastnameStartingWith(String lastname, Pageable pageRequest);
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ import com.mongodb.WriteResult;
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AbstracMongoQueryUnitTests {
|
||||
public class AbstractMongoQueryUnitTests {
|
||||
|
||||
@Mock RepositoryMetadata metadataMock;
|
||||
@Mock MongoOperations mongoOperationsMock;
|
||||
@@ -88,7 +88,8 @@ public class AbstracMongoQueryUnitTests {
|
||||
|
||||
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),
|
||||
Matchers.anyString());
|
||||
}
|
||||
@@ -122,19 +123,21 @@ public class AbstracMongoQueryUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-566
|
||||
* @see DATAMONGO-978
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() {
|
||||
|
||||
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);
|
||||
|
||||
MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class);
|
||||
query.setDeleteQuery(true);
|
||||
|
||||
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) {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
* 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.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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.QPerson;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mysema.query.types.expr.BooleanOperation;
|
||||
@@ -43,6 +46,7 @@ import com.mysema.query.types.path.StringPath;
|
||||
* Unit tests for {@link SpringDataMongodbSerializer}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SpringDataMongodbSerializerUnitTests {
|
||||
@@ -132,6 +136,41 @@ public class SpringDataMongodbSerializerUnitTests {
|
||||
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 {
|
||||
String id;
|
||||
String street;
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.internal.AssumptionViolatedException;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.data.util.Version;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.mongodb.BasicDBObjectBuilder;
|
||||
import com.mongodb.CommandResult;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
|
||||
/**
|
||||
* {@link TestRule} verifying server tests are executed against match a given version. This one can be used as
|
||||
* {@link ClassRule} eg. in context depending tests run with {@link SpringJUnit4ClassRunner} when the context would fail
|
||||
* to start in case of invalid version, or as simple {@link Rule} on specific tests.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
public class MongoVersionRule implements TestRule {
|
||||
|
||||
private String host = "localhost";
|
||||
private int port = 27017;
|
||||
|
||||
private final Version minVersion;
|
||||
private final Version maxVersion;
|
||||
|
||||
private Version currentVersion;
|
||||
|
||||
public MongoVersionRule(Version min, Version max) {
|
||||
this.minVersion = min;
|
||||
this.maxVersion = max;
|
||||
}
|
||||
|
||||
public static MongoVersionRule any() {
|
||||
return new MongoVersionRule(new Version(0, 0, 0), new Version(9999, 9999, 9999));
|
||||
}
|
||||
|
||||
public static MongoVersionRule atLeast(Version minVersion) {
|
||||
return new MongoVersionRule(minVersion, new Version(9999, 9999, 9999));
|
||||
}
|
||||
|
||||
public static MongoVersionRule atMost(Version maxVersion) {
|
||||
return new MongoVersionRule(new Version(0, 0, 0), maxVersion);
|
||||
}
|
||||
|
||||
public MongoVersionRule withServerRunningAt(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(final Statement base, Description description) {
|
||||
|
||||
initCurrentVersion();
|
||||
return new Statement() {
|
||||
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
if (currentVersion != null) {
|
||||
if (currentVersion.isLessThan(minVersion) || currentVersion.isGreaterThan(maxVersion)) {
|
||||
throw new AssumptionViolatedException(String.format(
|
||||
"Expected mongodb server to be in range %s to %s but found %s", minVersion, maxVersion, currentVersion));
|
||||
}
|
||||
}
|
||||
base.evaluate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void initCurrentVersion() {
|
||||
|
||||
if (currentVersion == null) {
|
||||
try {
|
||||
MongoClient client;
|
||||
client = new MongoClient(host, port);
|
||||
DB db = client.getDB("test");
|
||||
CommandResult result = db.command(new BasicDBObjectBuilder().add("buildInfo", 1).get());
|
||||
this.currentVersion = Version.parse(result.get("version").toString());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ Export-Template:
|
||||
org.springframework.data.mongodb.*;version="${project.version}"
|
||||
Import-Template:
|
||||
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,
|
||||
javax.annotation.processing.*;version="0",
|
||||
javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional,
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<xi:include href="introduction/introduction.xml"/>
|
||||
<xi:include href="introduction/requirements.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.9.0.M1/src/docbkx/repositories.xml">
|
||||
<xi:fallback href="../../../spring-data-commons/src/docbkx/repositories.xml" />
|
||||
</xi:include>
|
||||
</part>
|
||||
@@ -88,10 +88,10 @@
|
||||
<part id="appendix">
|
||||
<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.9.0.M1/src/docbkx/repository-namespace-reference.xml">
|
||||
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-namespace-reference.xml" />
|
||||
</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.9.0.M1/src/docbkx/repository-query-keywords-reference.xml">
|
||||
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-query-keywords-reference.xml" />
|
||||
</xi:include>
|
||||
</part>
|
||||
|
||||
@@ -18,20 +18,19 @@
|
||||
<section id="get-started:help:community">
|
||||
<title>Community Forum</title>
|
||||
|
||||
<para>The Spring Data <ulink
|
||||
url="http://forum.spring.io/forum/spring-projects/data">forum
|
||||
</ulink> is a message board for all Spring Data (not just Document)
|
||||
users to share information and help each other. Note that registration
|
||||
is needed <emphasis>only</emphasis> for posting.</para>
|
||||
<para>Spring Data on Stackoverflow <ulink
|
||||
url="http://stackoverflow.com/questions/tagged/spring-data">Stackoverflow
|
||||
</ulink> is a tag for all Spring Data (not just Document) users to share
|
||||
information and help each other. Note that registration is needed
|
||||
<emphasis>only</emphasis> for posting.</para>
|
||||
</section>
|
||||
|
||||
<section id="get-started:help:professional">
|
||||
<title>Professional Support</title>
|
||||
|
||||
<para>Professional, from-the-source support, with guaranteed response
|
||||
time, is available from <ulink
|
||||
url="http://gopivotal.com/">Pivotal Sofware, Inc.</ulink>, the company
|
||||
behind Spring Data and Spring.</para>
|
||||
time, is available from <ulink url="http://gopivotal.com/">Pivotal
|
||||
Sofware, Inc.</ulink>, the company behind Spring Data and Spring.</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -44,22 +43,21 @@
|
||||
homepage</ulink>.</para>
|
||||
|
||||
<para>You can help make Spring Data best serve the needs of the Spring
|
||||
community by interacting with developers through the Spring Community
|
||||
<ulink url="http://forum.spring.io/">forums</ulink>. To follow
|
||||
developer activity look for the mailing list information on the Spring
|
||||
Data Mongo homepage.</para>
|
||||
community by interacting with developers through the Community on <ulink
|
||||
url="http://stackoverflow.com/questions/tagged/spring-data">Stackoverflow</ulink>.
|
||||
To follow developer activity look for the mailing list information on the
|
||||
Spring Data Mongo homepage.</para>
|
||||
|
||||
<para>If you encounter a bug or want to suggest an improvement, please
|
||||
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
|
||||
Spring eco system, subscribe to the Spring Community <ulink
|
||||
url="https://spring.io">Portal</ulink>.</para>
|
||||
|
||||
<para>Lastly, you can follow the SpringSource Data <ulink
|
||||
url="https://spring.io/blog">blog </ulink>or
|
||||
the project team on Twitter (<ulink
|
||||
url="http://twitter.com/SpringData">SpringData</ulink>)</para>
|
||||
url="https://spring.io/blog">blog </ulink>or the project team on Twitter
|
||||
(<ulink url="http://twitter.com/SpringData">SpringData</ulink>).</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -357,6 +357,16 @@ public class Person {
|
||||
level to describe how to geoindex the field.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>@TextIndexed</literal> - applied at the field level
|
||||
to mark the field to be included in the text index.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>@Language</literal> - applied at the field level to
|
||||
set the language override property for text index.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>@Transient</literal> - by default all private fields
|
||||
are mapped to the document, this annotation excludes the field where
|
||||
@@ -526,7 +536,7 @@ OrderItem item = converter.read(OrderItem.class, input);</programlisting>
|
||||
test suite.</para>
|
||||
</section>
|
||||
|
||||
<section id="mapping-usage-indexes">
|
||||
<section id="mapping-usage-indexes.compound-index">
|
||||
<title>Compound Indexes</title>
|
||||
|
||||
<para>Compound indexes are also supported. They are defined at the class
|
||||
@@ -561,6 +571,51 @@ public class Person {
|
||||
</example></para>
|
||||
</section>
|
||||
|
||||
<section id="mapping-usage-indexes.text-index">
|
||||
<title>Text Indexes</title>
|
||||
|
||||
<note>
|
||||
<para>The text index feature is disabled by default for mongodb
|
||||
v.2.4.</para>
|
||||
</note>
|
||||
|
||||
<para>Creating a text index allows to accumulate several fields into a
|
||||
searchable full text index. It is only possible to have one text index
|
||||
per collection so all fields marked with
|
||||
<interfacename>@TextIndexed</interfacename> are combined into this
|
||||
index. Properties can be weighted to influence document score for
|
||||
ranking results. The default language for the text index is english, to
|
||||
change the default language set
|
||||
<interfacename>@Document(language="spanish")</interfacename> to any
|
||||
language you want. Using a property called <literal>language</literal>
|
||||
or <interfacename>@Language</interfacename> allows to define a language
|
||||
override on a per document base.</para>
|
||||
|
||||
<example>
|
||||
<title>Example Text Index Usage</title>
|
||||
|
||||
<programlisting language="java">@Document(language = "spanish")
|
||||
class SomeEntity {
|
||||
|
||||
@TextIndexed String foo;
|
||||
|
||||
@Language String lang;
|
||||
|
||||
Nested nested;
|
||||
|
||||
|
||||
}
|
||||
|
||||
class Nested {
|
||||
|
||||
@TextIndexed(weight=5) String bar;
|
||||
|
||||
String roo;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
</section>
|
||||
|
||||
<section id="mapping-usage-references">
|
||||
<title>Using DBRefs</title>
|
||||
|
||||
|
||||
@@ -2016,6 +2016,67 @@ GeoResults<Restaurant> = operations.geoNear(query, Restaurant.class);</pro
|
||||
origin.</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="mongo.textsearch">
|
||||
<title>Full Text Queries</title>
|
||||
|
||||
<para>Since mongodb 2.6 full text queries can be excuted using the
|
||||
<literal>$text</literal> operator. Methods and operations specific for
|
||||
full text queries are available in <classname>TextQuery</classname> and
|
||||
<classname>TextCriteria</classname>. When doing full text search please
|
||||
refer to the <ulink
|
||||
url="http://docs.mongodb.org/manual/reference/operator/query/text/#behavior">mongodb
|
||||
reference</ulink> for its behavior and limitations.</para>
|
||||
|
||||
<example>
|
||||
<title>Full Text Search</title>
|
||||
|
||||
<para>Bevore we are actually able to use full text search we have to
|
||||
ensure to set up the search index correctly. Please refer to section
|
||||
<link linkend="mapping-usage-indexes.text-index">Text Index</link> for
|
||||
creating index structures.</para>
|
||||
|
||||
<programlisting language="javascript">db.foo.ensureIndex(
|
||||
{
|
||||
title : "text",
|
||||
content : "text"
|
||||
},
|
||||
{
|
||||
weights : {
|
||||
title : 3
|
||||
}
|
||||
}
|
||||
)
|
||||
</programlisting>
|
||||
|
||||
<para>A query searching for <literal>coffee cake</literal>, sorted by
|
||||
relevance according to the <literal>weights</literal> can be defined
|
||||
and executed as:</para>
|
||||
|
||||
<programlisting language="java">Query query = TextQuery.searching(new TextCriteria().matchingAny("coffee", "cake")).sortByScore();
|
||||
List<Document> page = template.find(query, Document.class);
|
||||
</programlisting>
|
||||
|
||||
<para>Exclusion of search terms can directly be done by prefixing the
|
||||
term with <literal>-</literal> or using
|
||||
<literal>notMatching</literal></para>
|
||||
|
||||
<programlisting language="java">// search for 'coffee' and not 'cake'
|
||||
TextQuery.searching(new TextCriteria().matching("coffee").matching("-cake"));
|
||||
TextQuery.searching(new TextCriteria().matching("coffee").notMatching("cake"));
|
||||
</programlisting>
|
||||
|
||||
<para>As <classname>TextCriteria.matching</classname> takes the
|
||||
provided term as is. Therefore phrases can be defined by putting them
|
||||
between double quotes (eg. <literal>\"coffee cake\")</literal> or
|
||||
using <classname>TextCriteria.phrase.</classname></para>
|
||||
|
||||
<programlisting language="java">// search for phrase 'coffee cake'
|
||||
TextQuery.searching(new TextCriteria().matching("\"coffee cake\""));
|
||||
TextQuery.searching(new TextCriteria().phrase("coffee cake"));
|
||||
</programlisting>
|
||||
</example>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="mongo.mapreduce">
|
||||
@@ -3046,11 +3107,12 @@ class MyConverter implements Converter<String, Person> { … }</programlis
|
||||
</listitem>
|
||||
</itemizedlist></para>
|
||||
|
||||
<para>You can create both standard indexes and geospatial indexes using
|
||||
the classes <classname>IndexDefinition</classname> and
|
||||
<classname>GeoSpatialIndex</classname> respectfully. For example, given
|
||||
the Venue class defined in a previous section, you would declare a
|
||||
geospatial query as shown below</para>
|
||||
<para>You can create standard, geospatial and text indexes using the
|
||||
classes <classname>IndexDefinition</classname>,
|
||||
<classname>GeoSpatialIndex</classname> and
|
||||
<classname>TextIndexDefinition</classname>. For example, given the Venue
|
||||
class defined in a previous section, you would declare a geospatial
|
||||
query as shown below.</para>
|
||||
|
||||
<programlisting language="java">mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location"));</programlisting>
|
||||
</section>
|
||||
|
||||
@@ -1,6 +1,57 @@
|
||||
Spring Data MongoDB Changelog
|
||||
=============================
|
||||
|
||||
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.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)
|
||||
---------------------------------------------
|
||||
* DATAMONGO-936 - Release 1.5 GA.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Spring Data MongoDB 1.5 GA
|
||||
Spring Data MongoDB 1.6 M1
|
||||
Copyright (c) [2010-2014] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
|
||||
Reference in New Issue
Block a user