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.
This commit is contained in:
committed by
Oliver Gierke
parent
ece655f67d
commit
693f5ddf6e
@@ -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 {
|
||||
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 {
|
||||
*/
|
||||
@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 {
|
||||
* @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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
* @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 {
|
||||
*/
|
||||
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 {
|
||||
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 {
|
||||
* @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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
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 {
|
||||
this.conversionService = converter.getConversionService();
|
||||
this.converter = converter;
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
this.exampleMapper = new MongoExampleMapper(converter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
*/
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 @@
|
||||
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;
|
||||
* Utility methods for JSON serialization.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
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
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
return delegate.getFullText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Example<?> getSampleObject() {
|
||||
return delegate.getSampleObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value with the underlying {@link MongoWriter}.
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
* @since 1.8
|
||||
*/
|
||||
Object[] getValues();
|
||||
|
||||
/**
|
||||
* Get the sample for query by example
|
||||
*
|
||||
* @return
|
||||
* @since 1.8
|
||||
*/
|
||||
Example<?> getSampleObject();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
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>
|
||||
} 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>
|
||||
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>
|
||||
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>
|
||||
@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>
|
||||
return parameter.getParameterAnnotation(Near.class) != null;
|
||||
}
|
||||
|
||||
private boolean isExample() {
|
||||
return Example.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
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> {
|
||||
@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> {
|
||||
|
||||
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> {
|
||||
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> {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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
|
||||
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
|
||||
private static int tryDetermineRealSizeOrReturn(Iterable<?> iterable, int defaultSize) {
|
||||
return iterable == null ? 0 : (iterable instanceof Collection) ? ((Collection<?>) iterable).size() : defaultSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.*;
|
||||
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 {
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* <<<<<<< HEAD
|
||||
*
|
||||
* @see DATAMONGO-1269
|
||||
*/
|
||||
@Test
|
||||
@@ -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;
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
public Class<?> getDynamicProjection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getSampleObject()
|
||||
*/
|
||||
@Override
|
||||
public Example<?> getSampleObject() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user