Browse Source

DATAMONGO-1245 - Add support for Query By Example.

An explorative approach to QBE trying find possibilities and limitations. We now support querying documents by providing a sample of the given object holding compare values. For the sake of partial matching we flatten out nested structures so we can create different queries for matching like:

{ _id : 1, nested : { value : "conflux" } }
{ _id : 1, nested.value : { "conflux" } }

This is useful when you want so search using a only partially filled nested document. String matching can be configured to wrap strings with $regex which creates { firstname : { $regex : "^foo", $options: "i" } } when using StringMatchMode.STARTING along with the ignoreCaseOption. DBRefs and geo structures such as Point or GeoJsonPoint is converted to their according structure.

Related tickets: DATACMNS-810.
Original pull request: #341.
pull/346/merge
Christoph Strobl 11 years ago committed by Oliver Gierke
parent
commit
693f5ddf6e
  1. 110
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  2. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java
  3. 229
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java
  4. 25
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
  5. 47
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java
  6. 101
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java
  7. 68
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java
  8. 35
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java
  9. 6
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java
  10. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java
  11. 24
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java
  12. 13
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java
  13. 81
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java
  14. 53
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java
  15. 76
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java
  16. 449
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java
  17. 37
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java
  18. 174
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java
  19. 93
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  20. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java
  21. 7
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  22. 10
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java
  23. 237
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java

110
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -51,6 +51,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator; @@ -51,6 +51,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.annotation.Id;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.domain.Example;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
@ -340,8 +341,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -340,8 +341,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
DBCursor cursor = collection.find(mappedQuery, mappedFields);
QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType);
ReadDbObjectCallback<T> readCallback = new ReadDbObjectCallback<T>(mongoConverter, entityType,
collection.getName());
ReadDbObjectCallback<T> readCallback = new ReadDbObjectCallback<T>(mongoConverter, entityType, collection
.getName());
return new CloseableIterableCursorAdapter<T>(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
}
@ -374,8 +375,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -374,8 +375,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
*/
@Deprecated
public CommandResult executeCommand(final DBObject command, final int options) {
return executeCommand(command,
(options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred() : ReadPreference.primary());
return executeCommand(command, (options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred()
: ReadPreference.primary());
}
/*
@ -421,8 +422,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -421,8 +422,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
* @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply
* limits, skips and so on).
*/
protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch,
CursorPreparer preparer) {
protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer) {
Assert.notNull(query);
@ -637,6 +637,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -637,6 +637,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return doFindOne(collectionName, new BasicDBObject(idKey, id), null, entityClass);
}
public <S extends T, T> List<T> findByExample(S sample) {
return findByExample(new Example<S>(sample));
}
@SuppressWarnings("unchecked")
public <S extends T, T> List<T> findByExample(Example<S> sample) {
Assert.notNull(sample, "Sample object must not be null!");
return (List<T>) find(new Query(new Criteria().alike(sample)), sample.getSampleType());
}
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass) {
return geoNear(near, entityClass, determineCollectionName(entityClass));
}
@ -672,8 +683,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -672,8 +683,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
List<Object> results = (List<Object>) commandResult.get("results");
results = results == null ? Collections.emptyList() : results;
DbObjectCallback<GeoResult<T>> callback = new GeoNearResultDbObjectCallback<T>(
new ReadDbObjectCallback<T>(mongoConverter, entityClass, collectionName), near.getMetric());
DbObjectCallback<GeoResult<T>> callback = new GeoNearResultDbObjectCallback<T>(new ReadDbObjectCallback<T>(
mongoConverter, entityClass, collectionName), near.getMetric());
List<GeoResult<T>> result = new ArrayList<GeoResult<T>>(results.size());
int index = 0;
@ -749,9 +760,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -749,9 +760,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
public long count(Query query, Class<?> entityClass, String collectionName) {
Assert.hasText(collectionName);
final DBObject dbObject = query == null ? null
: queryMapper.getMappedObject(query.getQueryObject(),
entityClass == null ? null : mappingContext.getPersistentEntity(entityClass));
final DBObject dbObject = query == null ? null : queryMapper.getMappedObject(query.getQueryObject(),
entityClass == null ? null : mappingContext.getPersistentEntity(entityClass));
return execute(collectionName, new CollectionCallback<Long>() {
public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException {
@ -1030,8 +1040,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1030,8 +1040,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName,
entityClass, dbDoc, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc)
: collection.insert(dbDoc, writeConcernToUse);
WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc) : collection.insert(dbDoc,
writeConcernToUse);
handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.INSERT);
return dbDoc.get(ID_FIELD);
}
@ -1052,8 +1062,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1052,8 +1062,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null,
null, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList)
: collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse);
WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList) : collection.insert(
dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse);
handleAnyWriteResultErrors(writeResult, null, MongoActionOperation.INSERT_LIST);
return null;
}
@ -1083,8 +1093,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1083,8 +1093,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass,
dbDoc, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc)
: collection.save(dbDoc, writeConcernToUse);
WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc) : collection.save(dbDoc,
writeConcernToUse);
handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.SAVE);
return dbDoc.get(ID_FIELD);
}
@ -1137,10 +1147,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1137,10 +1147,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
increaseVersionForUpdateIfNecessary(entity, update);
DBObject queryObj = query == null ? new BasicDBObject()
: queryMapper.getMappedObject(query.getQueryObject(), entity);
DBObject updateObj = update == null ? new BasicDBObject()
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
DBObject queryObj = query == null ? new BasicDBObject() : queryMapper.getMappedObject(query.getQueryObject(),
entity);
DBObject updateObj = update == null ? new BasicDBObject() : updateMapper.getMappedObject(
update.getUpdateObject(), entity);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Calling update using query: {} and update: {} in collection: {}",
@ -1281,9 +1291,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1281,9 +1291,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty);
if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) {
throw new InvalidDataAccessApiUsageException(
String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(),
entity.getClass().getName()));
throw new InvalidDataAccessApiUsageException(String.format(
"Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), entity.getClass()
.getName()));
}
}
@ -1322,12 +1332,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1322,12 +1332,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Remove using query: {} in collection: {}.",
new Object[] { serializeToJsonSafely(dboq), collection.getName() });
LOGGER.debug("Remove using query: {} in collection: {}.", new Object[] { serializeToJsonSafely(dboq),
collection.getName() });
}
WriteResult wr = writeConcernToUse == null ? collection.remove(dboq)
: collection.remove(dboq, writeConcernToUse);
WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) : collection.remove(dboq,
writeConcernToUse);
handleAnyWriteResultErrors(wr, dboq, MongoActionOperation.REMOVE);
@ -1343,8 +1353,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1343,8 +1353,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
public <T> List<T> findAll(Class<T> entityClass, String collectionName) {
return executeFindMultiInternal(new FindCallback(null), null,
new ReadDbObjectCallback<T>(mongoConverter, entityClass, collectionName), collectionName);
return executeFindMultiInternal(new FindCallback(null), null, new ReadDbObjectCallback<T>(mongoConverter,
entityClass, collectionName), collectionName);
}
public <T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapFunction, String reduceFunction,
@ -1360,8 +1370,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1360,8 +1370,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction,
String reduceFunction, Class<T> entityClass) {
return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(),
entityClass);
return mapReduce(query, inputCollectionName, mapFunction, reduceFunction,
new MapReduceOptions().outputTypeInline(), entityClass);
}
public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction,
@ -1372,9 +1382,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1372,9 +1382,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
DBCollection inputCollection = getCollection(inputCollectionName);
MapReduceCommand command = new MapReduceCommand(inputCollection, mapFunc, reduceFunc,
mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(),
query == null || query.getQueryObject() == null ? null
: queryMapper.getMappedObject(query.getQueryObject(), null));
mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(), query == null
|| query.getQueryObject() == null ? null : queryMapper.getMappedObject(query.getQueryObject(), null));
copyMapReduceOptionsToCommand(query, mapReduceOptions, command);
@ -1710,8 +1719,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1710,8 +1719,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
mappedFields, entityClass, collectionName);
}
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields),
new ReadDbObjectCallback<T>(this.mongoConverter, entityClass, collectionName), collectionName);
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), new ReadDbObjectCallback<T>(
this.mongoConverter, entityClass, collectionName), collectionName);
}
/**
@ -1725,8 +1734,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1725,8 +1734,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
* @return the List of converted objects.
*/
protected <T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<T> entityClass) {
return doFind(collectionName, query, fields, entityClass, null,
new ReadDbObjectCallback<T>(this.mongoConverter, entityClass, collectionName));
return doFind(collectionName, query, fields, entityClass, null, new ReadDbObjectCallback<T>(this.mongoConverter,
entityClass, collectionName));
}
/**
@ -1744,8 +1753,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1744,8 +1753,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
*/
protected <T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<T> entityClass,
CursorPreparer preparer) {
return doFind(collectionName, query, fields, entityClass, preparer,
new ReadDbObjectCallback<T>(mongoConverter, entityClass, collectionName));
return doFind(collectionName, query, fields, entityClass, preparer, new ReadDbObjectCallback<T>(mongoConverter,
entityClass, collectionName));
}
protected <S, T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<S> entityClass,
@ -1898,8 +1907,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1898,8 +1907,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
DbObjectCallback<T> objectCallback, String collectionName) {
try {
T result = objectCallback
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)));
T result = objectCallback.doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(),
collectionName)));
return result;
} catch (RuntimeException e) {
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
@ -1924,8 +1933,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -1924,8 +1933,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
* @param collectionName the collection to be queried
* @return
*/
private <T> List<T> executeFindMultiInternal(CollectionCallback<DBCursor> collectionCallback, CursorPreparer preparer,
DbObjectCallback<T> objectCallback, String collectionName) {
private <T> List<T> executeFindMultiInternal(CollectionCallback<DBCursor> collectionCallback,
CursorPreparer preparer, DbObjectCallback<T> objectCallback, String collectionName) {
try {
@ -2015,8 +2024,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -2015,8 +2024,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
if (entity == null) {
throw new InvalidDataAccessApiUsageException(
"No Persistent Entity information found for the class " + entityClass.getName());
throw new InvalidDataAccessApiUsageException("No Persistent Entity information found for the class "
+ entityClass.getName());
}
return entity.getCollection();
}
@ -2080,8 +2089,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -2080,8 +2089,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
String error = result.getErrorMessage();
error = error == null ? "NO MESSAGE" : error;
throw new InvalidDataAccessApiUsageException(
"Command execution failed: Error [" + error + "], Command = " + source, ex);
throw new InvalidDataAccessApiUsageException("Command execution failed: Error [" + error + "], Command = "
+ source, ex);
}
}
@ -2277,8 +2286,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -2277,8 +2286,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
class UnwrapAndReadDbObjectCallback<T> extends ReadDbObjectCallback<T> {
public UnwrapAndReadDbObjectCallback(EntityReader<? super T, DBObject> reader, Class<T> type,
String collectionName) {
public UnwrapAndReadDbObjectCallback(EntityReader<? super T, DBObject> reader, Class<T> type, String collectionName) {
super(reader, type, collectionName);
}

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java

@ -117,6 +117,10 @@ abstract class GeoConverters { @@ -117,6 +117,10 @@ abstract class GeoConverters {
Assert.isTrue(source.keySet().size() == 2, "Source must contain 2 elements");
if (source.containsField("type")) {
return DbObjectToGeoJsonPointConverter.INSTANCE.convert(source);
}
return new Point((Double) source.get("x"), (Double) source.get("y"));
}
}

229
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java

@ -0,0 +1,229 @@ @@ -0,0 +1,229 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Pattern;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Example.NullHandler;
import org.springframework.data.domain.Example.StringMatcher;
import org.springframework.data.domain.PropertySpecifier;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.MongoRegexCreator;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* @author Christoph Strobl
* @since 1.8
*/
public class MongoExampleMapper {
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final MongoConverter converter;
public MongoExampleMapper(MongoConverter converter) {
this.converter = converter;
this.mappingContext = converter.getMappingContext();
}
/**
* Returns the given {@link Example} as {@link DBObject} holding matching values extracted from
* {@link Example#getProbe()}.
*
* @param example
* @return
* @since 1.8
*/
public DBObject getMappedExample(Example<?> example) {
return getMappedExample(example, mappingContext.getPersistentEntity(example.getSampleType()));
}
/**
* Returns the given {@link Example} as {@link DBObject} holding matching values extracted from
* {@link Example#getProbe()}.
*
* @param example
* @param entity
* @return
* @since 1.8
*/
public DBObject getMappedExample(Example<?> example, MongoPersistentEntity<?> entity) {
DBObject reference = (DBObject) converter.convertToMongoType(example.getSampleObject());
if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getSampleObject()).getIdentifier() == null) {
reference.removeField(entity.getIdProperty().getFieldName());
}
applyPropertySpecs("", reference, example);
return ObjectUtils.nullSafeEquals(NullHandler.INCLUDE, example.getNullHandler()) ? reference : new BasicDBObject(
SerializationUtils.flatMap(reference));
}
private String getMappedPropertyPath(String path, Example<?> example) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(example.getSampleType());
Iterator<String> parts = Arrays.asList(path.split("\\.")).iterator();
final Stack<MongoPersistentProperty> stack = new Stack<MongoPersistentProperty>();
List<String> resultParts = new ArrayList<String>();
while (parts.hasNext()) {
final String part = parts.next();
MongoPersistentProperty prop = entity.getPersistentProperty(part);
if (prop == null) {
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@Override
public void doWithPersistentProperty(MongoPersistentProperty property) {
if (property.getFieldName().equals(part)) {
stack.push(property);
}
}
});
if (stack.isEmpty()) {
return "";
}
prop = stack.pop();
}
resultParts.add(prop.getName());
if (prop.isEntity() && mappingContext.hasPersistentEntityFor(prop.getActualType())) {
entity = mappingContext.getPersistentEntity(prop.getActualType());
} else {
break;
}
}
return StringUtils.collectionToDelimitedString(resultParts, ".");
}
private void applyPropertySpecs(String path, DBObject source, Example<?> example) {
if (!(source instanceof BasicDBObject)) {
return;
}
Iterator<Map.Entry<String, Object>> iter = ((BasicDBObject) source).entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Object> entry = iter.next();
if (entry.getKey().equals("_id") && entry.getValue() == null) {
iter.remove();
continue;
}
String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey();
String mappedPropertyPath = getMappedPropertyPath(propertyPath, example);
if (example.isIgnoredPath(propertyPath) || example.isIgnoredPath(mappedPropertyPath)) {
iter.remove();
continue;
}
PropertySpecifier specifier = null;
StringMatcher stringMatcher = example.getDefaultStringMatcher();
Object value = entry.getValue();
boolean ignoreCase = example.isIngnoreCaseEnabled();
if (example.hasPropertySpecifiers()) {
mappedPropertyPath = example.hasPropertySpecifier(propertyPath) ? propertyPath : getMappedPropertyPath(
propertyPath, example);
specifier = example.getPropertySpecifier(mappedPropertyPath);
if (specifier != null) {
if (specifier.hasStringMatcher()) {
stringMatcher = specifier.getStringMatcher();
}
if (specifier.getIgnoreCase() != null) {
ignoreCase = specifier.getIgnoreCase();
}
}
}
// TODO: should a PropertySpecifier outrule the later on string matching?
if (specifier != null) {
value = specifier.transformValue(value);
if (value == null) {
iter.remove();
continue;
}
entry.setValue(value);
}
if (entry.getValue() instanceof String) {
applyStringMatcher(entry, stringMatcher, ignoreCase);
} else if (entry.getValue() instanceof BasicDBObject) {
applyPropertySpecs(propertyPath, (BasicDBObject) entry.getValue(), example);
}
}
}
private void applyStringMatcher(Map.Entry<String, Object> entry, StringMatcher stringMatcher, boolean ignoreCase) {
BasicDBObject dbo = new BasicDBObject();
if (ObjectUtils.nullSafeEquals(StringMatcher.DEFAULT, stringMatcher)) {
if (ignoreCase) {
dbo.put("$regex", Pattern.quote((String) entry.getValue()));
entry.setValue(dbo);
}
} else {
String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(),
stringMatcher.getPartType());
dbo.put("$regex", expression);
entry.setValue(dbo);
}
if (ignoreCase) {
dbo.put("$options", "i");
}
}
}

25
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

@ -27,6 +27,7 @@ import org.bson.types.ObjectId; @@ -27,6 +27,7 @@ import org.bson.types.ObjectId;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Example;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PropertyPath;
@ -70,6 +71,7 @@ public class QueryMapper { @@ -70,6 +71,7 @@ public class QueryMapper {
private final ConversionService conversionService;
private final MongoConverter converter;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final MongoExampleMapper exampleMapper;
/**
* Creates a new {@link QueryMapper} with the given {@link MongoConverter}.
@ -83,6 +85,7 @@ public class QueryMapper { @@ -83,6 +85,7 @@ public class QueryMapper {
this.conversionService = converter.getConversionService();
this.converter = converter;
this.mappingContext = converter.getMappingContext();
this.exampleMapper = new MongoExampleMapper(converter);
}
/**
@ -239,6 +242,10 @@ public class QueryMapper { @@ -239,6 +242,10 @@ public class QueryMapper {
return new BasicDBObject(keyword.getKey(), newConditions);
}
if (keyword.isSample()) {
return exampleMapper.getMappedExample(keyword.<Example<?>> getValue(), entity);
}
return new BasicDBObject(keyword.getKey(), convertSimpleOrDBObject(keyword.getValue(), entity));
}
@ -254,8 +261,8 @@ public class QueryMapper { @@ -254,8 +261,8 @@ public class QueryMapper {
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists();
Object value = keyword.getValue();
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property)
: getMappedValue(property.with(keyword.getKey()), value);
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property) : getMappedValue(
property.with(keyword.getKey()), value);
return new BasicDBObject(keyword.key, convertedValue);
}
@ -477,8 +484,8 @@ public class QueryMapper { @@ -477,8 +484,8 @@ public class QueryMapper {
}
try {
return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService.convert(id, ObjectId.class)
: delegateConvertToMongoType(id, null);
return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService
.convert(id, ObjectId.class) : delegateConvertToMongoType(id, null);
} catch (ConversionException o_O) {
return delegateConvertToMongoType(id, null);
}
@ -566,6 +573,16 @@ public class QueryMapper { @@ -566,6 +573,16 @@ public class QueryMapper {
return "$geometry".equalsIgnoreCase(key);
}
/**
* Returns wheter the current keyword indicates a sample object.
*
* @return
* @since 1.8
*/
public boolean isSample() {
return "$sample".equalsIgnoreCase(key);
}
public boolean hasIterableValue() {
return value instanceof Iterable;
}

47
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java

@ -26,6 +26,7 @@ import java.util.Map.Entry; @@ -26,6 +26,7 @@ import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.bson.BSON;
import org.springframework.data.domain.Example;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Shape;
@ -88,6 +89,30 @@ public class Criteria implements CriteriaDefinition { @@ -88,6 +89,30 @@ public class Criteria implements CriteriaDefinition {
return new Criteria(key);
}
/**
* Static factory method to create a {@link Criteria} matching an example object.
*
* @param example must not be {@literal null}.
* @return
* @see Criteria#alike(Example)
* @since 1.8
*/
public static Criteria byExample(Object example) {
return byExample(new Example<Object>(example));
}
/**
* Static factory method to create a {@link Criteria} matching an example object.
*
* @param example must not be {@literal null}.
* @return
* @see Criteria#alike(Example)
* @since 1.8
*/
public static Criteria byExample(Example<?> example) {
return new Criteria().alike(example);
}
/**
* Static factory method to create a Criteria using the provided key
*
@ -191,8 +216,8 @@ public class Criteria implements CriteriaDefinition { @@ -191,8 +216,8 @@ public class Criteria implements CriteriaDefinition {
*/
public Criteria in(Object... o) {
if (o.length > 1 && o[1] instanceof Collection) {
throw new InvalidMongoDbApiUsageException(
"You can only pass in one argument of type " + o[1].getClass().getName());
throw new InvalidMongoDbApiUsageException("You can only pass in one argument of type "
+ o[1].getClass().getName());
}
criteria.put("$in", Arrays.asList(o));
return this;
@ -498,6 +523,20 @@ public class Criteria implements CriteriaDefinition { @@ -498,6 +523,20 @@ public class Criteria implements CriteriaDefinition {
return this;
}
/**
* Creates a criterion using the given object as a pattern.
*
* @param sample
* @return
* @since 1.8
*/
public Criteria alike(Example<?> sample) {
criteria.put("$sample", sample);
this.criteriaChain.add(this);
return this;
}
/**
* Creates an 'or' criteria using the $or operator for all of the provided criteria
* <p>
@ -543,8 +582,8 @@ public class Criteria implements CriteriaDefinition { @@ -543,8 +582,8 @@ public class Criteria implements CriteriaDefinition {
private Criteria registerCriteriaChainElement(Criteria criteria) {
if (lastOperatorWasNot()) {
throw new IllegalArgumentException(
"operator $not is not allowed around criteria chain element: " + criteria.getCriteriaObject());
throw new IllegalArgumentException("operator $not is not allowed around criteria chain element: "
+ criteria.getCriteriaObject());
} else {
criteriaChain.add(criteria);
}

101
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java

@ -0,0 +1,101 @@ @@ -0,0 +1,101 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.query;
import java.util.regex.Pattern;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
* @since 1.8
*/
public enum MongoRegexCreator {
INSTANCE;
private static final Pattern PUNCTATION_PATTERN = Pattern.compile("\\p{Punct}");
/**
* Creates a regular expression String to be used with {@code $regex}.
*
* @param source the plain String
* @param type
* @return {@literal source} when {@literal source} or {@literal type} is {@literal null}.
*/
public String toRegularExpression(String source, Type type) {
if (type == null || source == null) {
return source;
}
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, type);
switch (type) {
case STARTING_WITH:
regex = "^" + regex;
break;
case ENDING_WITH:
regex = regex + "$";
break;
case CONTAINING:
case NOT_CONTAINING:
regex = ".*" + regex + ".*";
break;
case SIMPLE_PROPERTY:
case NEGATING_SIMPLE_PROPERTY:
regex = "^" + regex + "$";
default:
}
return regex;
}
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Type type) {
if (!ObjectUtils.nullSafeEquals(Type.LIKE, type)) {
return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source;
}
if (source.equals("*")) {
return ".*";
}
StringBuilder sb = new StringBuilder();
boolean leadingWildcard = source.startsWith("*");
boolean trailingWildcard = source.endsWith("*");
String valueToUse = source.substring(leadingWildcard ? 1 : 0,
trailingWildcard ? source.length() - 1 : source.length());
if (PUNCTATION_PATTERN.matcher(valueToUse).find()) {
valueToUse = Pattern.quote(valueToUse);
}
if (leadingWildcard) {
sb.append(".*");
}
sb.append(valueToUse);
if (trailingWildcard) {
sb.append(".*");
}
return sb.toString();
}
}

68
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,15 @@ @@ -16,12 +16,15 @@
package org.springframework.data.mongodb.core.query;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.core.convert.converter.Converter;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
@ -29,6 +32,7 @@ import com.mongodb.util.JSON; @@ -29,6 +32,7 @@ import com.mongodb.util.JSON;
* Utility methods for JSON serialization.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public abstract class SerializationUtils {
@ -36,6 +40,68 @@ public abstract class SerializationUtils { @@ -36,6 +40,68 @@ public abstract class SerializationUtils {
}
/**
* Flattens out a given {@link DBObject}.
*
* <pre>
* <code>
* {
* _id : 1
* nested : { value : "conflux"}
* }
* </code>
* will result in
* <code>
* {
* _id : 1
* nested.value : "conflux"
* }
* </code>
* </pre>
*
* @param source can be {@literal null}.
* @return {@link Collections#emptyMap()} when source is {@literal null}
* @since 1.8
*/
public static Map<String, Object> flatMap(DBObject source) {
if (source == null) {
return Collections.emptyMap();
}
Map<String, Object> result = new HashMap<String, Object>();
toFlatMap("", source, result);
return result;
}
private static void toFlatMap(String currentPath, Object source, Map<String, Object> map) {
if (source instanceof BasicDBObject) {
BasicDBObject dbo = (BasicDBObject) source;
Iterator<Map.Entry<String, Object>> iter = dbo.entrySet().iterator();
String pathPrefix = currentPath.isEmpty() ? "" : currentPath + ".";
while (iter.hasNext()) {
Map.Entry<String, Object> entry = iter.next();
if (entry.getKey().startsWith("$")) {
if (map.containsKey(currentPath)) {
((BasicDBObject) map.get(currentPath)).put(entry.getKey(), entry.getValue());
} else {
map.put(currentPath, new BasicDBObject(entry.getKey(), entry.getValue()));
}
} else {
toFlatMap(pathPrefix + entry.getKey(), entry.getValue(), map);
}
}
} else {
map.put(currentPath, source);
}
}
/**
* Serializes the given object into pseudo-JSON meaning it's trying to create a JSON representation as far as possible
* but falling back to the given object's {@link Object#toString()} method if it's not serializable. Useful for

35
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2010-2014 the original author or authors.
* Copyright 2010-2016 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,9 @@ package org.springframework.data.mongodb.repository; @@ -18,6 +18,9 @@ package org.springframework.data.mongodb.repository;
import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
@ -71,4 +74,34 @@ public interface MongoRepository<T, ID extends Serializable> extends PagingAndSo @@ -71,4 +74,34 @@ public interface MongoRepository<T, ID extends Serializable> extends PagingAndSo
* @since 1.7
*/
<S extends T> List<S> insert(Iterable<S> entities);
/**
* Returns all instances of the type specified by the given {@link Example}.
*
* @param example must not be {@literal null}.
* @return
* @since 1.8
*/
<S extends T> List<T> findAllByExample(Example<S> example);
/**
* Returns all instances of the type specified by the given {@link Example}.
*
* @param example must not be {@literal null}.
* @param sort can be {@literal null}.
* @return all entities sorted by the given options
* @since 1.8
*/
<S extends T> List<T> findAllByExample(Example<S> example, Sort sort);
/**
* Returns a {@link Page} of entities meeting the paging restriction specified by the given {@link Example} limited to
* criteria provided in the {@code Pageable} object.
*
* @param example must not be {@literal null}.
* @param pageable can be {@literal null}.
* @return a {@link Page} of entities
* @since 1.8
*/
<S extends T> Page<T> findAllByExample(Example<S> example, Pageable pageable);
}

6
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java

@ -21,6 +21,7 @@ import java.util.Collections; @@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Sort;
@ -132,6 +133,11 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { @@ -132,6 +133,11 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
return delegate.getFullText();
}
@Override
public Example<?> getSampleObject() {
return delegate.getSampleObject();
}
/**
* Converts the given value with the underlying {@link MongoWriter}.
*

9
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.repository.query;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Range;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
@ -60,4 +61,12 @@ public interface MongoParameterAccessor extends ParameterAccessor { @@ -60,4 +61,12 @@ public interface MongoParameterAccessor extends ParameterAccessor {
* @since 1.8
*/
Object[] getValues();
/**
* Get the sample for query by example
*
* @return
* @since 1.8
*/
Example<?> getSampleObject();
}

24
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java

@ -20,6 +20,7 @@ import java.util.Arrays; @@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Range;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
@ -42,6 +43,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -42,6 +43,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
private final int rangeIndex;
private final int maxDistanceIndex;
private final Integer fullTextIndex;
private final int sampleObjectIndex;
private Integer nearIndex;
@ -69,10 +71,12 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -69,10 +71,12 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
} else if (this.nearIndex == null) {
this.nearIndex = -1;
}
this.sampleObjectIndex = parameterTypes.indexOf(Example.class);
}
private MongoParameters(List<MongoParameter> parameters, int maxDistanceIndex, Integer nearIndex,
Integer fullTextIndex, int rangeIndex) {
Integer fullTextIndex, int rangeIndex, int sampleObjectIndex) {
super(parameters);
@ -80,6 +84,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -80,6 +84,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
this.fullTextIndex = fullTextIndex;
this.maxDistanceIndex = maxDistanceIndex;
this.rangeIndex = rangeIndex;
this.sampleObjectIndex = sampleObjectIndex;
}
private final int getNearIndex(List<Class<?>> parameterTypes) {
@ -182,13 +187,22 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -182,13 +187,22 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
return rangeIndex;
}
/**
* @return
* @since 1.8
*/
public int getSampleObjectParameterIndex() {
return sampleObjectIndex;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.Parameters#createFrom(java.util.List)
*/
@Override
protected MongoParameters createFrom(List<MongoParameter> parameters) {
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex);
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex,
this.sampleObjectIndex);
}
private int getTypeIndex(List<TypeInformation<?>> parameterTypes, Class<?> type, Class<?> componentType) {
@ -240,7 +254,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -240,7 +254,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
@Override
public boolean isSpecialParameter() {
return super.isSpecialParameter() || Distance.class.isAssignableFrom(getType()) || isNearParameter()
|| TextCriteria.class.isAssignableFrom(getType());
|| TextCriteria.class.isAssignableFrom(getType()) || isExample();
}
private boolean isNearParameter() {
@ -260,6 +274,10 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -260,6 +274,10 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
return parameter.getParameterAnnotation(Near.class) != null;
}
private boolean isExample() {
return Example.class.isAssignableFrom(getType());
}
}
}

13
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query; @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query;
import java.util.Arrays;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Range;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
@ -125,9 +126,15 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso @@ -125,9 +126,15 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso
return ((TextCriteria) fullText);
}
throw new IllegalArgumentException(
String.format("Expected full text parameter to be one of String, Term or TextCriteria but found %s.",
ClassUtils.getShortName(fullText.getClass())));
throw new IllegalArgumentException(String.format(
"Expected full text parameter to be one of String, Term or TextCriteria but found %s.",
ClassUtils.getShortName(fullText.getClass())));
}
public Example<?> getSampleObject() {
int index = method.getParameters().getSampleObjectParameterIndex();
return index >= 0 ? (Example<?>) getValue(index) : null;
}
/*

81
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java

@ -38,6 +38,7 @@ import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; @@ -38,6 +38,7 @@ import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.MongoRegexCreator;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
@ -46,7 +47,6 @@ import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; @@ -46,7 +47,6 @@ import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Custom query creator to create Mongo criterias.
@ -151,7 +151,20 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { @@ -151,7 +151,20 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
@Override
protected Query complete(Criteria criteria, Sort sort) {
Query query = (criteria == null ? new Query() : new Query(criteria)).with(sort);
Criteria toUse = null;
if (accessor.getSampleObject() != null) {
toUse = new Criteria().alike(accessor.getSampleObject());
}
if (criteria != null) {
if (toUse == null) {
toUse = criteria;
} else {
toUse.andOperator(criteria);
}
}
Query query = (toUse == null ? new Query() : new Query(toUse)).with(sort);
if (LOG.isDebugEnabled()) {
LOG.debug("Created query " + query);
@ -285,8 +298,8 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { @@ -285,8 +298,8 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
case ALWAYS:
if (path.getType() != String.class) {
throw new IllegalArgumentException(
String.format("Part %s must be of type String but was %s", path, path.getType()));
throw new IllegalArgumentException(String.format("Part %s must be of type String but was %s", path,
path.getType()));
}
// fall-through
@ -372,8 +385,8 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { @@ -372,8 +385,8 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
return (T) parameter;
}
throw new IllegalArgumentException(
String.format("Expected parameter type of %s but got %s!", type, parameter.getClass()));
throw new IllegalArgumentException(String.format("Expected parameter type of %s but got %s!", type,
parameter.getClass()));
}
private Object[] nextAsArray(Iterator<Object> iterator) {
@ -390,61 +403,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { @@ -390,61 +403,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
}
private String toLikeRegex(String source, Part part) {
Type type = part.getType();
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, part);
switch (type) {
case STARTING_WITH:
regex = "^" + regex;
break;
case ENDING_WITH:
regex = regex + "$";
break;
case CONTAINING:
case NOT_CONTAINING:
regex = ".*" + regex + ".*";
break;
case SIMPLE_PROPERTY:
case NEGATING_SIMPLE_PROPERTY:
regex = "^" + regex + "$";
default:
}
return regex;
}
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Part qpart) {
if (!ObjectUtils.nullSafeEquals(Type.LIKE, qpart.getType())) {
return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source;
}
if ("*".equals(source)) {
return ".*";
}
StringBuilder sb = new StringBuilder();
boolean leadingWildcard = source.startsWith("*");
boolean trailingWildcard = source.endsWith("*");
String valueToUse = source.substring(leadingWildcard ? 1 : 0,
trailingWildcard ? source.length() - 1 : source.length());
if (PUNCTATION_PATTERN.matcher(valueToUse).find()) {
valueToUse = Pattern.quote(valueToUse);
}
if (leadingWildcard) {
sb.append(".*");
}
sb.append(valueToUse);
if (trailingWildcard) {
sb.append(".*");
}
return sb.toString();
return MongoRegexCreator.INSTANCE.toRegularExpression(source, part.getType());
}
private boolean isSpherical(MongoPersistentProperty property) {

53
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java

@ -25,6 +25,7 @@ import java.util.HashSet; @@ -25,6 +25,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
@ -259,6 +260,57 @@ public class SimpleMongoRepository<T, ID extends Serializable> implements MongoR @@ -259,6 +260,57 @@ public class SimpleMongoRepository<T, ID extends Serializable> implements MongoR
return list;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoRepository#findAllByExample(org.springframework.data.domain.Example, org.springframework.data.domain.Pageable)
*/
@Override
public <S extends T> Page<T> findAllByExample(Example<S> example, Pageable pageable) {
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example)).with(pageable);
long count = mongoOperations.count(q, entityInformation.getJavaType(), entityInformation.getCollectionName());
if (count == 0) {
return new PageImpl<T>(Collections.<T> emptyList());
}
return new PageImpl<T>(mongoOperations.find(q, entityInformation.getJavaType(),
entityInformation.getCollectionName()), pageable, count);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoRepository#findAllByExample(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
*/
@Override
public <S extends T> List<T> findAllByExample(Example<S> example, Sort sort) {
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example));
if (sort != null) {
q.with(sort);
}
return findAll(q);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoRepository#findAllByExample(org.springframework.data.domain.Example)
*/
@Override
public <S extends T> List<T> findAllByExample(Example<S> example) {
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example));
return findAll(q);
}
private List<T> findAll(Query query) {
if (query == null) {
@ -291,4 +343,5 @@ public class SimpleMongoRepository<T, ID extends Serializable> implements MongoR @@ -291,4 +343,5 @@ public class SimpleMongoRepository<T, ID extends Serializable> implements MongoR
private static int tryDetermineRealSizeOrReturn(Iterable<?> iterable, int defaultSize) {
return iterable == null ? 0 : (iterable instanceof Collection) ? ((Collection<?>) iterable).size() : defaultSize;
}
}

76
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,18 +20,24 @@ import static org.junit.Assert.*; @@ -20,18 +20,24 @@ import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import java.util.Arrays;
import java.util.Map;
import org.hamcrest.Matcher;
import org.hamcrest.collection.IsMapContaining;
import org.hamcrest.core.Is;
import org.junit.Test;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
/**
* Unit tests for {@link SerializationUtils}.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class SerializationUtilsUnitTests {
@ -60,6 +66,74 @@ public class SerializationUtilsUnitTests { @@ -60,6 +66,74 @@ public class SerializationUtilsUnitTests {
assertThat(serializeToJsonSafely(dbObject), is(expectedOutput));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void flatMapShouldFlatOutNestedStructureCorrectly() {
DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("value", "conflux")).get();
assertThat(flatMap(dbo), IsMapContaining.<String, Object> hasEntry("_id", 1));
assertThat(flatMap(dbo), IsMapContaining.<String, Object> hasEntry("nested.value", "conflux"));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void flatMapShouldFlatOutNestedStructureWithListCorrectly() {
BasicDBList dbl = new BasicDBList();
dbl.addAll(Arrays.asList("nightwielder", "calamity"));
DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("value", dbl)).get();
assertThat(flatMap(dbo), IsMapContaining.<String, Object> hasEntry("_id", 1));
assertThat(flatMap(dbo), IsMapContaining.<String, Object> hasEntry("nested.value", dbl));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void flatMapShouldLeaveKeywordsUntouched() {
DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("$regex", "^conflux$"))
.get();
Map<String, Object> map = flatMap(dbo);
assertThat(map, IsMapContaining.<String, Object> hasEntry("_id", 1));
assertThat(map.get("nested"), notNullValue());
assertThat(((Map<String, Object>) map.get("nested")).get("$regex"), Is.<Object> is("^conflux$"));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void flatMapSouldAppendCommandsCorrectly() {
DBObject dbo = new BasicDBObjectBuilder().add("_id", 1)
.add("nested", new BasicDBObjectBuilder().add("$regex", "^conflux$").add("$options", "i").get()).get();
Map<String, Object> map = flatMap(dbo);
assertThat(map, IsMapContaining.<String, Object> hasEntry("_id", 1));
assertThat(map.get("nested"), notNullValue());
assertThat(((Map<String, Object>) map.get("nested")).get("$regex"), Is.<Object> is("^conflux$"));
assertThat(((Map<String, Object>) map.get("nested")).get("$options"), Is.<Object> is("i"));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void flatMapShouldReturnEmptyMapWhenSourceIsNull() {
assertThat(flatMap(null).isEmpty(), is(true));
}
static class Complex {
}

449
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java

@ -0,0 +1,449 @@ @@ -0,0 +1,449 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.domain.Example.*;
import static org.springframework.data.domain.PropertySpecifier.*;
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import org.hamcrest.core.Is;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Example;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.ClassWithGeoTypes;
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.WithDBRef;
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.MongoMappingContext;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoExampleMapperUnitTests {
MongoExampleMapper mapper;
MongoMappingContext context;
MappingMongoConverter converter;
@Mock MongoDbFactory factory;
@Before
public void setUp() {
this.context = new MongoMappingContext();
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context);
this.converter.afterPropertiesSet();
this.mapper = new MongoExampleMapper(converter);
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() {
FlatDocument probe = new FlatDocument();
probe.id = "steelheart";
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("_id", "steelheart").get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() {
FlatDocument probe = new FlatDocument();
probe.id = "steelheart";
probe.stringValue = "firefight";
probe.intValue = 100;
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class));
assertThat(dbo,
is(new BasicDBObjectBuilder().add("_id", "steelheart").add("stringValue", "firefight").add("intValue", 100)
.get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
probe.intValue = 100;
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "firefight").add("intValue", 100).get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() {
FlatDocument probe = new FlatDocument();
probe.listOfString = Arrays.asList("Prof", "Tia", "David");
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("listOfString", Arrays.asList("Prof", "Tia", "David")).get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() {
FlatDocument probe = new FlatDocument();
probe.customNamedField = "Mitosis";
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("custom_field_name", "Mitosis").get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenienMatchMode() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
probe.flatDoc.stringValue = "conflux";
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WrapperDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "conflux").get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStriktMatchMode() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
probe.flatDoc.stringValue = "conflux";
Example<?> example = newExampleOf(probe).includeNullValues().get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class));
assertThat(dbo, isBsonObject().containing("flatDoc.stringValue", "conflux"));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
probe.intValue = 100;
Example<?> example = newExampleOf(probe).matchStringsStartingWith().get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
assertThat(dbo,
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "^firefight"))
.add("intValue", 100).get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
probe.intValue = 100;
Example<?> example = newExampleOf(probe).matchStringsEndingWith().get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
assertThat(dbo,
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "firefight$"))
.add("intValue", 100).get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
probe.intValue = 100;
Example<?> example = newExampleOf(probe).matchStringsEndingWith().matchStringsWithIgnoreCase().get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
assertThat(
dbo,
is(new BasicDBObjectBuilder()
.add("stringValue", new BasicDBObjectBuilder().add("$regex", "firefight$").add("$options", "i").get())
.add("intValue", 100).get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
probe.intValue = 100;
Example<?> example = newExampleOf(probe).matchStringsWithIgnoreCase().get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
assertThat(
dbo,
is(new BasicDBObjectBuilder()
.add("stringValue",
new BasicDBObjectBuilder().add("$regex", Pattern.quote("firefight")).add("$options", "i").get())
.add("intValue", 100).get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedWhenContainingDBRef() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "steelheart";
probe.referenceDocument = new ReferenceDocument();
probe.referenceDocument.id = "200";
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WithDBRef.class));
com.mongodb.DBRef reference = getTypedValue(dbo, "referenceDocument", com.mongodb.DBRef.class);
assertThat(reference.getId(), Is.<Object> is("200"));
assertThat(reference.getCollectionName(), is("refDoc"));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedWhenDBRefIsNull() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "steelheart";
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "steelheart").get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() {
ClassWithGeoTypes probe = new ClassWithGeoTypes();
probe.legacyPoint = new Point(10D, 20D);
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WithDBRef.class));
assertThat(dbo.get("legacyPoint.x"), Is.<Object> is(10D));
assertThat(dbo.get("legacyPoint.y"), Is.<Object> is(20D));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void mappingShouldExcludeFieldWithCustomNameCorrectly() {
FlatDocument probe = new FlatDocument();
probe.customNamedField = "foo";
probe.intValue = 10;
probe.stringValue = "string";
Example<?> example = newExampleOf(probe).ignore("customNamedField").get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "string").add("intValue", 10).get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void mappingShouldExcludeFieldCorrectly() {
FlatDocument probe = new FlatDocument();
probe.customNamedField = "foo";
probe.intValue = 10;
probe.stringValue = "string";
Example<?> example = newExampleOf(probe).ignore("stringValue").get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("custom_field_name", "foo").add("intValue", 10).get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void mappingShouldExcludeNestedFieldCorrectly() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
probe.flatDoc.customNamedField = "foo";
probe.flatDoc.intValue = 10;
probe.flatDoc.stringValue = "string";
Example<?> example = newExampleOf(probe).ignore("flatDoc.stringValue").get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.custom_field_name", "foo").add("flatDoc.intValue", 10)
.get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() {
WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument();
probe.flatDoc.customNamedField = "foo";
probe.flatDoc.intValue = 10;
probe.flatDoc.stringValue = "string";
Example<?> example = newExampleOf(probe).ignore("flatDoc.customNamedField").get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "string").add("flatDoc.intValue", 10)
.get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() {
FlatDocument probe = new FlatDocument();
probe.stringValue = "firefight";
probe.customNamedField = "steelheart";
Example<?> example = newExampleOf(probe).specify(newPropertySpecifier("stringValue").matchStringContaining().get())
.get();
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
assertThat(
dbo,
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", ".*firefight.*"))
.add("custom_field_name", "steelheart").get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void mappingShouldIncludePropertiesFromHierarchicalDocument() {
HierachicalDocument probe = new HierachicalDocument();
probe.stringValue = "firefight";
probe.customNamedField = "steelheart";
probe.anotherStringValue = "calamity";
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class));
assertThat(dbo, isBsonObject().containing("anotherStringValue", "calamity"));
}
static class FlatDocument {
@Id String id;
String stringValue;
@Field("custom_field_name") String customNamedField;
Integer intValue;
List<String> listOfString;
@DBRef ReferenceDocument referenceDocument;
}
static class HierachicalDocument extends FlatDocument {
String anotherStringValue;
}
static class WrapperDocument {
@Id String id;
FlatDocument flatDoc;
}
@Document(collection = "refDoc")
static class ReferenceDocument {
@Id String id;
String value;
}
}

37
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java

@ -29,6 +29,7 @@ import java.util.List; @@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import org.bson.types.ObjectId;
import org.hamcrest.core.Is;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -791,6 +792,8 @@ public class QueryMapperUnitTests { @@ -791,6 +792,8 @@ public class QueryMapperUnitTests {
}
/**
* <<<<<<< HEAD
*
* @see DATAMONGO-1269
*/
@Test
@ -818,6 +821,40 @@ public class QueryMapperUnitTests { @@ -818,6 +821,40 @@ public class QueryMapperUnitTests {
assertThat(dbo.containsField("list.1.stringProperty"), is(true));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectly() {
Foo probe = new Foo();
probe.embedded = new EmbeddedClass();
probe.embedded.id = "conflux";
Query query = query(byExample(probe));
DBObject dbo = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(Foo.class));
assertThat(dbo, is(new BasicDBObjectBuilder().add("embedded._id", "conflux").get()));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() {
ClassWithGeoTypes probe = new ClassWithGeoTypes();
probe.legacyPoint = new Point(10D, 20D);
Query query = query(byExample(probe));
DBObject dbo = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(WithDBRef.class));
assertThat(dbo.get("legacyPoint.x"), Is.<Object> is(10D));
assertThat(dbo.get("legacyPoint.y"), Is.<Object> is(20D));
}
@Document
public class Foo {
@Id private ObjectId id;

174
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java

@ -0,0 +1,174 @@ @@ -0,0 +1,174 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.temp;
import java.net.UnknownHostException;
import java.util.List;
import org.hamcrest.core.Is;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Example;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.MongoClient;
public class QueryByExampleTests {
MongoTemplate template;
@Before
public void setUp() throws UnknownHostException {
template = new MongoTemplate(new MongoClient(), "query-by-example");
template.remove(new Query(), Person.class);
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findByExampleShouldWorkForSimpleProperty() {
init();
Person sample = new Person();
sample.lastname = "stark";
List<Person> result = template.findByExample(sample);
Assert.assertThat(result.size(), Is.is(2));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findByExampleShouldWorkForMultipleProperties() {
init();
Person sample = new Person();
sample.lastname = "stark";
sample.firstname = "arya";
List<Person> result = template.findByExample(sample);
Assert.assertThat(result.size(), Is.is(1));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findByExampleShouldWorkForIdProperty() {
init();
Person p4 = new Person();
template.save(p4);
Person sample = new Person();
sample.id = p4.id;
List<Person> result = template.findByExample(sample);
Assert.assertThat(result.size(), Is.is(1));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findByExampleShouldReturnEmptyListIfNotMatching() {
init();
Person sample = new Person();
sample.firstname = "jon";
sample.firstname = "stark";
List<Person> result = template.findByExample(sample);
Assert.assertThat(result.size(), Is.is(0));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findByExampleShouldReturnEverythingWhenSampleIsEmpty() {
init();
Person sample = new Person();
List<Person> result = template.findByExample(sample);
Assert.assertThat(result.size(), Is.is(3));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findByExampleWithCriteria() {
init();
Person sample = new Person();
sample.lastname = "stark";
Query query = new Query(new Criteria().alike(new Example<Person>(sample)).and("firstname").regex("^ary*"));
List<Person> result = template.find(query, Person.class);
Assert.assertThat(result.size(), Is.is(1));
}
public void init() {
Person p1 = new Person();
p1.firstname = "bran";
p1.lastname = "stark";
Person p2 = new Person();
p2.firstname = "jon";
p2.lastname = "snow";
Person p3 = new Person();
p3.firstname = "arya";
p3.lastname = "stark";
template.save(p1);
template.save(p2);
template.save(p3);
}
@Document(collection = "dramatis-personae")
static class Person {
@Id String id;
String firstname;
@Field("last_name") String lastname;
@Override
public String toString() {
return "Person [id=" + id + ", firstname=" + firstname + ", lastname=" + lastname + "]";
}
}
}

93
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -28,12 +28,15 @@ import java.util.stream.Collectors; @@ -28,12 +28,15 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.hamcrest.Matchers;
import org.hamcrest.core.Is;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Range;
@ -55,6 +58,7 @@ import org.springframework.data.mongodb.repository.Person.Sex; @@ -55,6 +58,7 @@ import org.springframework.data.mongodb.repository.Person.Sex;
import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension.SampleSecurityContextHolder;
import org.springframework.data.querydsl.QSort;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Base class for tests for {@link PersonRepository}.
@ -175,8 +179,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -175,8 +179,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@Test
public void executesPagedFinderCorrectly() throws Exception {
Page<Person> page = repository.findByLastnameLike("*a*",
new PageRequest(0, 2, Direction.ASC, "lastname", "firstname"));
Page<Person> page = repository.findByLastnameLike("*a*", new PageRequest(0, 2, Direction.ASC, "lastname",
"firstname"));
assertThat(page.isFirst(), is(true));
assertThat(page.isLast(), is(false));
assertThat(page.getNumberOfElements(), is(2));
@ -186,8 +190,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -186,8 +190,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@Test
public void executesPagedFinderWithAnnotatedQueryCorrectly() throws Exception {
Page<Person> page = repository.findByLastnameLikeWithPageable(".*a.*",
new PageRequest(0, 2, Direction.ASC, "lastname", "firstname"));
Page<Person> page = repository.findByLastnameLikeWithPageable(".*a.*", new PageRequest(0, 2, Direction.ASC,
"lastname", "firstname"));
assertThat(page.isFirst(), is(true));
assertThat(page.isLast(), is(false));
assertThat(page.getNumberOfElements(), is(2));
@ -311,8 +315,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -311,8 +315,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@Test
public void findsPagedPeopleByPredicate() throws Exception {
Page<Person> page = repository.findAll(person.lastname.contains("a"),
new PageRequest(0, 2, Direction.ASC, "lastname"));
Page<Person> page = repository.findAll(person.lastname.contains("a"), new PageRequest(0, 2, Direction.ASC,
"lastname"));
assertThat(page.isFirst(), is(true));
assertThat(page.isLast(), is(false));
assertThat(page.getNumberOfElements(), is(2));
@ -398,8 +402,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -398,8 +402,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
dave.setLocation(point);
repository.save(dave);
GeoResults<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73),
new Distance(2000, Metrics.KILOMETERS));
GeoResults<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000,
Metrics.KILOMETERS));
assertThat(results.getContent().isEmpty(), is(false));
}
@ -410,8 +414,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -410,8 +414,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
dave.setLocation(point);
repository.save(dave);
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73),
new Distance(2000, Metrics.KILOMETERS), new PageRequest(0, 20));
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000,
Metrics.KILOMETERS), new PageRequest(0, 20));
assertThat(results.getContent().isEmpty(), is(false));
// DATAMONGO-607
@ -621,8 +625,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -621,8 +625,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
repository.save(Arrays.asList(dave, oliver, carter, boyd, leroi));
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73),
new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2));
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000,
Metrics.KILOMETERS), new PageRequest(1, 2));
assertThat(results.getContent().isEmpty(), is(false));
assertThat(results.getNumberOfElements(), is(2));
@ -646,8 +650,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -646,8 +650,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
repository.save(Arrays.asList(dave, oliver, carter));
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73),
new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2));
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000,
Metrics.KILOMETERS), new PageRequest(1, 2));
assertThat(results.getContent().isEmpty(), is(false));
assertThat(results.getNumberOfElements(), is(1));
assertThat(results.isFirst(), is(false));
@ -665,8 +669,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -665,8 +669,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
dave.setLocation(point);
repository.save(dave);
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73),
new Distance(2000, Metrics.KILOMETERS), new PageRequest(0, 2));
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000,
Metrics.KILOMETERS), new PageRequest(0, 2));
assertThat(results.getContent().isEmpty(), is(false));
assertThat(results.getNumberOfElements(), is(1));
@ -684,8 +688,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -684,8 +688,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
dave.setLocation(new Point(-73.99171, 40.738868));
repository.save(dave);
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73),
new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2));
GeoPage<Person> results = repository.findByLocationNear(new Point(-73.99, 40.73), new Distance(2000,
Metrics.KILOMETERS), new PageRequest(1, 2));
assertThat(results.getContent().isEmpty(), is(true));
assertThat(results.getNumberOfElements(), is(0));
@ -935,8 +939,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -935,8 +939,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@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")));
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));
}
@ -947,8 +951,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -947,8 +951,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@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")));
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));
}
@ -959,8 +963,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -959,8 +963,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@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")));
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));
}
@ -971,8 +975,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -971,8 +975,8 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@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")));
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));
}
@ -1221,4 +1225,41 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -1221,4 +1225,41 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
assertThat(users, hasSize(1));
assertThat(users.get(0), is(dave));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findByExampleShouldResolveStuffCorrectly() {
Person sample = new Person();
sample.setLastname("Matthews");
// needed to tweak stuff a bit since some field are automatically set - so we need to undo this
ReflectionTestUtils.setField(sample, "id", null);
ReflectionTestUtils.setField(sample, "createdAt", null);
ReflectionTestUtils.setField(sample, "email", null);
Page<Person> result = repository.findAllByExample(new Example<Person>(sample), new PageRequest(0, 10));
Assert.assertThat(result.getNumberOfElements(), Is.is(2));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldResolveStuffCorrectly() {
Person sample = new Person();
sample.setLastname("Matthews");
// needed to tweak stuff a bit since some field are automatically set - so we need to undo this
ReflectionTestUtils.setField(sample, "id", null);
ReflectionTestUtils.setField(sample, "createdAt", null);
ReflectionTestUtils.setField(sample, "email", null);
List<Person> result = repository.findAllByExample(new Example<Person>(sample));
Assert.assertThat(result.size(), Is.is(2));
}
}

3
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java

@ -27,8 +27,7 @@ import org.springframework.data.mongodb.core.mapping.Document; @@ -27,8 +27,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
@Document
public abstract class Contact {
@Id
protected final String id;
@Id protected String id;
public Contact() {
this.id = new ObjectId().toString();

7
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -334,22 +334,23 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query @@ -334,22 +334,23 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
*/
@Query("{ firstname : { $in : ?0 }}")
Stream<Person> findByCustomQueryWithStreamingCursorByFirstnames(List<String> firstnames);
/**
* @see DATAMONGO-990
*/
@Query("{ firstname : ?#{[0]}}")
List<Person> findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname);
/**
* @see DATAMONGO-990
*/
@Query("{ firstname : ?#{[0]}, email: ?#{principal.email} }")
List<Person> findWithSpelByFirstnameAndCurrentUserWithCustomQuery(String firstname);
/**
* @see DATAMONGO-990
*/
@Query("{ firstname : :#{#firstname}}")
List<Person> findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname);
}

10
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query; @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query;
import java.util.Arrays;
import java.util.Iterator;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Sort;
@ -147,4 +148,13 @@ class StubParameterAccessor implements MongoParameterAccessor { @@ -147,4 +148,13 @@ class StubParameterAccessor implements MongoParameterAccessor {
public Class<?> getDynamicProjection() {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getSampleObject()
*/
@Override
public Example<?> getSampleObject() {
return null;
}
}

237
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2010-2014 the original author or authors.
* Copyright 2010-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,16 +31,26 @@ import org.junit.Before; @@ -31,16 +31,26 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.repository.Address;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.Person.Sex;
import org.springframework.data.mongodb.repository.User;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
/**
* @author <a href="mailto:kowsercse@gmail.com">A. B. M. Kowser</a>
* @author Thomas Darimont
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
@ -160,6 +170,224 @@ public class SimpleMongoRepositoryTests { @@ -160,6 +170,224 @@ public class SimpleMongoRepositoryTests {
assertThatAllReferencePersonsWereStoredCorrectly(idToPerson, saved);
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findByExampleShouldLookUpEntriesCorrectly() {
Person sample = new Person();
sample.setLastname("Matthews");
trimDomainType(sample, "id", "createdAt", "email");
Page<Person> result = repository.findAllByExample(new Example<Person>(sample), new PageRequest(0, 10));
assertThat(result.getContent(), hasItems(dave, oliver));
assertThat(result.getContent(), hasSize(2));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldLookUpEntriesCorrectly() {
Person sample = new Person();
sample.setLastname("Matthews");
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(new Example<Person>(sample));
assertThat(result, hasItems(dave, oliver));
assertThat(result, hasSize(2));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObject() {
dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington"));
repository.save(dave);
oliver.setAddress(new Address("East Capitol St NE & First St SE", "20004", "Washington"));
repository.save(oliver);
Person sample = new Person();
sample.setAddress(dave.getAddress());
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(new Example<Person>(sample));
assertThat(result, hasItem(dave));
assertThat(result, hasSize(1));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingPartialNestedObject() {
dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington"));
repository.save(dave);
oliver.setAddress(new Address("East Capitol St NE & First St SE", "20004", "Washington"));
repository.save(oliver);
Person sample = new Person();
sample.setAddress(new Address(null, null, "Washington"));
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(new Example<Person>(sample));
assertThat(result, hasItems(dave, oliver));
assertThat(result, hasSize(2));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldNotFindEntriesWhenUsingPartialNestedObjectInStrictMode() {
dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington"));
repository.save(dave);
Person sample = new Person();
sample.setAddress(new Address(null, null, "Washington"));
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(Example.newExampleOf(sample).includeNullValues().get());
assertThat(result, empty());
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObjectInStrictMode() {
dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington"));
repository.save(dave);
Person sample = new Person();
sample.setAddress(dave.getAddress());
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(Example.newExampleOf(sample).includeNullValues().get());
assertThat(result, hasItem(dave));
assertThat(result, hasSize(1));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldRespectStringMatchMode() {
Person sample = new Person();
sample.setLastname("Mat");
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(Example.newExampleOf(sample).matchStringsStartingWith().get());
assertThat(result, hasItems(dave, oliver));
assertThat(result, hasSize(2));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldResolveDbRefCorrectly() {
User user = new User();
user.setId("c0nf1ux");
user.setUsername("conflux");
template.save(user);
Person megan = new Person("megan", "tarash");
megan.setCreator(user);
repository.save(megan);
Person sample = new Person();
sample.setCreator(user);
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(new Example<Person>(sample));
assertThat(result, hasItem(megan));
assertThat(result, hasSize(1));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldResolveLegacyCoordinatesCorrectly() {
Person megan = new Person("megan", "tarash");
megan.setLocation(new Point(41.85003D, -87.65005D));
repository.save(megan);
Person sample = new Person();
sample.setLocation(megan.getLocation());
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(new Example<Person>(sample));
assertThat(result, hasItem(megan));
assertThat(result, hasSize(1));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldResolveGeoJsonCoordinatesCorrectly() {
Person megan = new Person("megan", "tarash");
megan.setLocation(new GeoJsonPoint(41.85003D, -87.65005D));
repository.save(megan);
Person sample = new Person();
sample.setLocation(megan.getLocation());
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(new Example<Person>(sample));
assertThat(result, hasItem(megan));
assertThat(result, hasSize(1));
}
/**
* @see DATAMONGO-1245
*/
@Test
public void findAllByExampleShouldProcessInheritanceCorrectly() {
PersonExtended sample = new PersonExtended() {};
sample.setLastname("Matthews");
trimDomainType(sample, "id", "createdAt", "email");
List<Person> result = repository.findAllByExample(new Example<PersonExtended>(sample));
assertThat(result, hasItems(dave, oliver));
assertThat(result, hasSize(2));
}
@Document(collection = "customizedPerson")
static class PersonExtended extends Person {
}
private void assertThatAllReferencePersonsWereStoredCorrectly(Map<String, Person> references, List<Person> saved) {
for (Person person : saved) {
@ -168,6 +396,13 @@ public class SimpleMongoRepositoryTests { @@ -168,6 +396,13 @@ public class SimpleMongoRepositoryTests {
}
}
private void trimDomainType(Object source, String... attributes) {
for (String attribute : attributes) {
ReflectionTestUtils.setField(source, attribute, null);
}
}
private static class CustomizedPersonInformation implements MongoEntityInformation<Person, String> {
@Override

Loading…
Cancel
Save