diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 3ee0cb0e1..10772f632 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -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 readCallback = new ReadDbObjectCallback(mongoConverter, entityType, - collection.getName()); + ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, entityType, collection + .getName()); return new CloseableIterableCursorAdapter(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 List findByExample(S sample) { + return findByExample(new Example(sample)); + } + + @SuppressWarnings("unchecked") + public List findByExample(Example sample) { + + Assert.notNull(sample, "Sample object must not be null!"); + return (List) find(new Query(new Criteria().alike(sample)), sample.getSampleType()); + } + public GeoResults geoNear(NearQuery near, Class entityClass) { return geoNear(near, entityClass, determineCollectionName(entityClass)); } @@ -672,8 +683,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { List results = (List) commandResult.get("results"); results = results == null ? Collections.emptyList() : results; - DbObjectCallback> callback = new GeoNearResultDbObjectCallback( - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), near.getMetric()); + DbObjectCallback> callback = new GeoNearResultDbObjectCallback(new ReadDbObjectCallback( + mongoConverter, entityClass, collectionName), near.getMetric()); List> result = new ArrayList>(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() { 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 List findAll(Class entityClass, String collectionName) { - return executeFindMultiInternal(new FindCallback(null), null, - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), collectionName); + return executeFindMultiInternal(new FindCallback(null), null, new ReadDbObjectCallback(mongoConverter, + entityClass, collectionName), collectionName); } public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, @@ -1360,8 +1370,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction, Class entityClass) { - return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), - entityClass); + return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, + new MapReduceOptions().outputTypeInline(), entityClass); } public MapReduceResults 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(this.mongoConverter, entityClass, collectionName), collectionName); + return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), new ReadDbObjectCallback( + this.mongoConverter, entityClass, collectionName), collectionName); } /** @@ -1725,8 +1734,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { * @return the List of converted objects. */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass) { - return doFind(collectionName, query, fields, entityClass, null, - new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName)); + return doFind(collectionName, query, fields, entityClass, null, new ReadDbObjectCallback(this.mongoConverter, + entityClass, collectionName)); } /** @@ -1744,8 +1753,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, CursorPreparer preparer) { - return doFind(collectionName, query, fields, entityClass, preparer, - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName)); + return doFind(collectionName, query, fields, entityClass, preparer, new ReadDbObjectCallback(mongoConverter, + entityClass, collectionName)); } protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, @@ -1898,8 +1907,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { DbObjectCallback 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 List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DbObjectCallback objectCallback, String collectionName) { + private List executeFindMultiInternal(CollectionCallback collectionCallback, + CursorPreparer preparer, DbObjectCallback 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 extends ReadDbObjectCallback { - public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, - String collectionName) { + public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, String collectionName) { super(reader, type, collectionName); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java index 7c6e1229c..bcbdef5e2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java @@ -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")); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java new file mode 100644 index 000000000..3332e4f56 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java @@ -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, 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 parts = Arrays.asList(path.split("\\.")).iterator(); + + final Stack stack = new Stack(); + + List resultParts = new ArrayList(); + + while (parts.hasNext()) { + + final String part = parts.next(); + MongoPersistentProperty prop = entity.getPersistentProperty(part); + + if (prop == null) { + + entity.doWithProperties(new PropertyHandler() { + + @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> iter = ((BasicDBObject) source).entrySet().iterator(); + + while (iter.hasNext()) { + + Map.Entry 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 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"); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index 490325461..34177102b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -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, 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.> 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; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java index aa67852ff..1bbf306a8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java @@ -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(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 *

@@ -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); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java new file mode 100644 index 000000000..4f7b4f042 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java @@ -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(); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java index b11948220..efc904b03 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/SerializationUtils.java @@ -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}. + * + *

+	 * 
+	 * {
+	 *   _id : 1
+	 *   nested : { value : "conflux"}
+	 * }
+	 * 
+	 * will result in 
+	 * 
+	 * {
+	 *   _id : 1
+	 *   nested.value : "conflux"
+	 * }
+	 * 
+	 * 
+ * + * @param source can be {@literal null}. + * @return {@link Collections#emptyMap()} when source is {@literal null} + * @since 1.8 + */ + public static Map flatMap(DBObject source) { + + if (source == null) { + return Collections.emptyMap(); + } + + Map result = new HashMap(); + toFlatMap("", source, result); + return result; + } + + private static void toFlatMap(String currentPath, Object source, Map map) { + + if (source instanceof BasicDBObject) { + + BasicDBObject dbo = (BasicDBObject) source; + Iterator> iter = dbo.entrySet().iterator(); + String pathPrefix = currentPath.isEmpty() ? "" : currentPath + "."; + + while (iter.hasNext()) { + + Map.Entry 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 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java index c056d61cb..35ec5f376 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java @@ -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 extends PagingAndSo * @since 1.7 */ List insert(Iterable entities); + + /** + * Returns all instances of the type specified by the given {@link Example}. + * + * @param example must not be {@literal null}. + * @return + * @since 1.8 + */ + List findAllByExample(Example 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 + */ + List findAllByExample(Example 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 + */ + Page findAllByExample(Example example, Pageable pageable); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java index 15d0cd25e..d6e8dfbd6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java @@ -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}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java index 6c67778e3..a64793e64 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java @@ -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(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java index 1e36047b9..317301145 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java @@ -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 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 } else if (this.nearIndex == null) { this.nearIndex = -1; } + + this.sampleObjectIndex = parameterTypes.indexOf(Example.class); } private MongoParameters(List 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 this.fullTextIndex = fullTextIndex; this.maxDistanceIndex = maxDistanceIndex; this.rangeIndex = rangeIndex; + this.sampleObjectIndex = sampleObjectIndex; } private final int getNearIndex(List> parameterTypes) { @@ -182,13 +187,22 @@ public class MongoParameters extends Parameters 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 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> parameterTypes, Class type, Class componentType) { @@ -240,7 +254,7 @@ public class MongoParameters extends Parameters @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 return parameter.getParameterAnnotation(Near.class) != null; } + private boolean isExample() { + return Example.class.isAssignableFrom(getType()); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java index 7f8ac79b4..9cbe003e3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query; 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; } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index caee21e2a..2ae0c30f3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -38,6 +38,7 @@ import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; 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 { @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 { 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 { 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 iterator) { @@ -390,61 +403,7 @@ class MongoQueryCreator extends AbstractQueryCreator { } 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) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java index 0b768343d..a87c184f5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java @@ -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 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 Page findAllByExample(Example 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(Collections. emptyList()); + } + return new PageImpl(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 List findAllByExample(Example 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 List findAllByExample(Example example) { + + Assert.notNull(example, "Sample must not be null!"); + + Query q = new Query(new Criteria().alike(example)); + + return findAll(q); + } + private List findAll(Query query) { if (query == null) { @@ -291,4 +343,5 @@ public class SimpleMongoRepository implements MongoR private static int tryDetermineRealSizeOrReturn(Iterable iterable, int defaultSize) { return iterable == null ? 0 : (iterable instanceof Collection) ? ((Collection) iterable).size() : defaultSize; } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java index 78ef19c03..231070d34 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java @@ -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. hasEntry("_id", 1)); + assertThat(flatMap(dbo), IsMapContaining. 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. hasEntry("_id", 1)); + assertThat(flatMap(dbo), IsMapContaining. 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 map = flatMap(dbo); + + assertThat(map, IsMapContaining. hasEntry("_id", 1)); + assertThat(map.get("nested"), notNullValue()); + assertThat(((Map) map.get("nested")).get("$regex"), Is. 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 map = flatMap(dbo); + + assertThat(map, IsMapContaining. hasEntry("_id", 1)); + assertThat(map.get("nested"), notNullValue()); + assertThat(((Map) map.get("nested")).get("$regex"), Is. is("^conflux$")); + assertThat(((Map) map.get("nested")).get("$options"), Is. is("i")); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void flatMapShouldReturnEmptyMapWhenSourceIsNull() { + assertThat(flatMap(null).isEmpty(), is(true)); + } + static class Complex { } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java new file mode 100644 index 000000000..956e6bc40 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java @@ -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. 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. is(10D)); + assertThat(dbo.get("legacyPoint.y"), Is. 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 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; + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index 8cf5852b7..80d9b98b1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -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. is(10D)); + assertThat(dbo.get("legacyPoint.y"), Is. is(20D)); + } + @Document public class Foo { @Id private ObjectId id; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java new file mode 100644 index 000000000..677ec3bb1 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/temp/QueryByExampleTests.java @@ -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 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 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 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 result = template.findByExample(sample); + Assert.assertThat(result.size(), Is.is(0)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void findByExampleShouldReturnEverythingWhenSampleIsEmpty() { + + init(); + + Person sample = new Person(); + + List 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(sample)).and("firstname").regex("^ary*")); + + List 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 + "]"; + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 6adc4bbc2..0f716fb6c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -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 page = repository.findByLastnameLike("*a*", - new PageRequest(0, 2, Direction.ASC, "lastname", "firstname")); + Page 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 page = repository.findByLastnameLikeWithPageable(".*a.*", - new PageRequest(0, 2, Direction.ASC, "lastname", "firstname")); + Page 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 page = repository.findAll(person.lastname.contains("a"), - new PageRequest(0, 2, Direction.ASC, "lastname")); + Page 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 results = repository.findByLocationNear(new Point(-73.99, 40.73), - new Distance(2000, Metrics.KILOMETERS)); + GeoResults 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 results = repository.findByLocationNear(new Point(-73.99, 40.73), - new Distance(2000, Metrics.KILOMETERS), new PageRequest(0, 20)); + GeoPage 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 results = repository.findByLocationNear(new Point(-73.99, 40.73), - new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2)); + GeoPage 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 results = repository.findByLocationNear(new Point(-73.99, 40.73), - new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2)); + GeoPage 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 results = repository.findByLocationNear(new Point(-73.99, 40.73), - new Distance(2000, Metrics.KILOMETERS), new PageRequest(0, 2)); + GeoPage 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 results = repository.findByLocationNear(new Point(-73.99, 40.73), - new Distance(2000, Metrics.KILOMETERS), new PageRequest(1, 2)); + GeoPage 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 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 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 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 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 result = repository.findAllByExample(new Example(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 result = repository.findAllByExample(new Example(sample)); + Assert.assertThat(result.size(), Is.is(2)); + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java index 1d15730af..1a7383601 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Contact.java @@ -27,8 +27,7 @@ import org.springframework.data.mongodb.core.mapping.Document; @Document public abstract class Contact { - @Id - protected final String id; + @Id protected String id; public Contact() { this.id = new ObjectId().toString(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index eae2c02e1..0796ba201 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -334,22 +334,23 @@ public interface PersonRepository extends MongoRepository, Query */ @Query("{ firstname : { $in : ?0 }}") Stream findByCustomQueryWithStreamingCursorByFirstnames(List firstnames); - + /** * @see DATAMONGO-990 */ @Query("{ firstname : ?#{[0]}}") List findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname); - + /** * @see DATAMONGO-990 */ @Query("{ firstname : ?#{[0]}, email: ?#{principal.email} }") List findWithSpelByFirstnameAndCurrentUserWithCustomQuery(String firstname); - + /** * @see DATAMONGO-990 */ @Query("{ firstname : :#{#firstname}}") List findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname); + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java index c13b27eb0..1be130373 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query; 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; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java index 5e9177ae3..6b75c51ed 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java @@ -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. B. M. Kowser * @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 result = repository.findAllByExample(new Example(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 result = repository.findAllByExample(new Example(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 result = repository.findAllByExample(new Example(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 result = repository.findAllByExample(new Example(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 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 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 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 result = repository.findAllByExample(new Example(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 result = repository.findAllByExample(new Example(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 result = repository.findAllByExample(new Example(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 result = repository.findAllByExample(new Example(sample)); + + assertThat(result, hasItems(dave, oliver)); + assertThat(result, hasSize(2)); + } + + @Document(collection = "customizedPerson") + static class PersonExtended extends Person { + + } + private void assertThatAllReferencePersonsWereStoredCorrectly(Map references, List 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 { @Override