> getValue(), entity);
+ }
+
return new BasicDBObject(keyword.getKey(), convertSimpleOrDBObject(keyword.getValue(), entity));
}
@@ -566,6 +573,16 @@ public boolean isGeometry() {
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 aa67852ffb..0836cdae9d 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.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 static Criteria where(String key) {
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(Example.of(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 Criteria gte(Object o) {
*/
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 Criteria elemMatch(Criteria c) {
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 Criteria andOperator(Criteria... criteria) {
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 0000000000..a1d85dff99
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2015-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.
+ * 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
+ * @author Mark Paluch
+ * @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.REGEX, type)) {
+ return source;
+ }
+
+ 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 b11948220c..4064b239b8 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-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.
@@ -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 @@
* Utility methods for JSON serialization.
*
* @author Oliver Gierke
+ * @author Christoph Strobl
*/
public abstract class SerializationUtils {
@@ -36,6 +40,68 @@ private 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 flattenMap(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 c056d61cb8..59b013d37a 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,19 +18,23 @@
import java.io.Serializable;
import java.util.List;
+import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
/**
* Mongo specific {@link org.springframework.data.repository.Repository} interface.
- *
+ *
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
+ * @author Mark Paluch
*/
@NoRepositoryBean
-public interface MongoRepository extends PagingAndSortingRepository {
+public interface MongoRepository
+ extends PagingAndSortingRepository, QueryByExampleExecutor {
/*
* (non-Javadoc)
@@ -54,7 +58,7 @@ public interface MongoRepository extends PagingAndSo
* Inserts the given a given entity. Assumes the instance to be new to be able to apply insertion optimizations. Use
* the returned instance for further operations as the save operation might have changed the entity instance
* completely. Prefer using {@link #save(Object)} instead to avoid the usage of store-specific API.
- *
+ *
* @param entity must not be {@literal null}.
* @return the saved entity
* @since 1.7
@@ -65,10 +69,21 @@ public interface MongoRepository extends PagingAndSo
* Inserts the given entities. Assumes the given entities to have not been persisted yet and thus will optimize the
* insert over a call to {@link #save(Iterable)}. Prefer using {@link #save(Iterable)} to avoid the usage of store
* specific API.
- *
+ *
* @param entities must not be {@literal null}.
* @return the saved entities
* @since 1.7
*/
List insert(Iterable entities);
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
+ */
+ List findAll(Example example);
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
+ */
+ List findAll(Example example, Sort sort);
+
}
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 a0cb9aa00a..f0ba85d924 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.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.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.
@@ -390,61 +390,7 @@ private Object[] nextAsArray(PotentiallyConvertingIterator iterator, MongoPersis
}
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 0b768343d8..fcca93e668 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
@@ -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.
@@ -25,10 +25,12 @@
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;
import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.TypedExampleSpec;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
@@ -43,6 +45,7 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
+ * @author Mark Paluch
*/
public class SimpleMongoRepository implements MongoRepository {
@@ -53,7 +56,7 @@ public class SimpleMongoRepository implements MongoR
* Creates a new {@link SimpleMongoRepository} for the given {@link MongoEntityInformation} and {@link MongoTemplate}.
*
* @param metadata must not be {@literal null}.
- * @param template must not be {@literal null}.
+ * @param mongoOperations must not be {@literal null}.
*/
public SimpleMongoRepository(MongoEntityInformation metadata, MongoOperations mongoOperations) {
@@ -259,6 +262,100 @@ public List insert(Iterable entities) {
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 findAll(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, getResultType(example), entityInformation.getCollectionName());
+ if (count == 0) {
+ return new PageImpl(Collections. emptyList());
+ }
+ return new PageImpl(mongoOperations.find(q, getResultType(example), 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 findAll(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 mongoOperations.find(q, getResultType(example), entityInformation.getCollectionName());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.repository.MongoRepository#findAllByExample(org.springframework.data.domain.Example)
+ */
+ @Override
+ public List findAll(Example example) {
+ return findAll(example, (Sort) null);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.QueryByExampleExecutor#findOne(org.springframework.data.domain.Example)
+ */
+ @Override
+ public S findOne(Example example) {
+
+ Assert.notNull(example, "Sample must not be null!");
+
+ Query q = new Query(new Criteria().alike(example));
+ return mongoOperations.findOne(q, getResultType(example), entityInformation.getCollectionName());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.QueryByExampleExecutor#count(org.springframework.data.domain.Example)
+ */
+ @Override
+ public long count(Example example) {
+
+ Assert.notNull(example, "Sample must not be null!");
+
+ Query q = new Query(new Criteria().alike(example));
+ return mongoOperations.count(q, getResultType(example), entityInformation.getCollectionName());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.QueryByExampleExecutor#exists(org.springframework.data.domain.Example)
+ */
+ @Override
+ public boolean exists(Example example) {
+
+ Assert.notNull(example, "Sample must not be null!");
+
+ Query q = new Query(new Criteria().alike(example));
+ return mongoOperations.exists(q, getResultType(example), entityInformation.getCollectionName());
+ }
+
+ private Class getResultType(Example example) {
+
+ if (example.getExampleSpec() instanceof TypedExampleSpec>) {
+ return example.getResultType();
+ }
+
+ return (Class) entityInformation.getJavaType();
+ }
+
private List findAll(Query query) {
if (query == null) {
@@ -291,4 +388,5 @@ private static List convertIterableToList(Iterable entities) {
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 78ef19c037..0b42b27c03 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-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.
@@ -20,18 +20,22 @@
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import java.util.Arrays;
+import java.util.Map;
import org.hamcrest.Matcher;
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 +64,74 @@ public void writesCollection() {
assertThat(serializeToJsonSafely(dbObject), is(expectedOutput));
}
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void flattenMapShouldFlatOutNestedStructureCorrectly() {
+
+ DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("value", "conflux")).get();
+
+ assertThat(flattenMap(dbo), hasEntry("_id", (Object) 1));
+ assertThat(flattenMap(dbo), hasEntry("nested.value", (Object) "conflux"));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void flattenMapShouldFlatOutNestedStructureWithListCorrectly() {
+
+ 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(flattenMap(dbo), hasEntry("_id", (Object) 1));
+ assertThat(flattenMap(dbo), hasEntry("nested.value", (Object) dbl));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void flattenMapShouldLeaveKeywordsUntouched() {
+
+ DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("$regex", "^conflux$"))
+ .get();
+
+ Map map = flattenMap(dbo);
+
+ assertThat(map, hasEntry("_id", (Object) 1));
+ assertThat(map.get("nested"), notNullValue());
+ assertThat(((Map) map.get("nested")).get("$regex"), is((Object) "^conflux$"));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void flattenMapShouldAppendCommandsCorrectly() {
+
+ DBObject dbo = new BasicDBObjectBuilder().add("_id", 1)
+ .add("nested", new BasicDBObjectBuilder().add("$regex", "^conflux$").add("$options", "i").get()).get();
+
+ Map map = flattenMap(dbo);
+
+ assertThat(map, hasEntry("_id", (Object) 1));
+ assertThat(map.get("nested"), notNullValue());
+ assertThat(((Map) map.get("nested")).get("$regex"), is((Object) "^conflux$"));
+ assertThat(((Map) map.get("nested")).get("$options"), is((Object) "i"));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void flattenMapShouldReturnEmptyMapWhenSourceIsNull() {
+ assertThat(flattenMap(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 0000000000..ba01bacaa0
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright 2015-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.
+ * 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.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.domain.ExampleSpec;
+import org.springframework.data.domain.ExampleSpec.GenericPropertyMatcher;
+import org.springframework.data.domain.ExampleSpec.StringMatcher;
+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
+ * @author Mark Paluch
+ */
+@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(of(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(of(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(of(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(of(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(of(probe), context.getPersistentEntity(FlatDocument.class));
+
+ assertThat(dbo, is(new BasicDBObjectBuilder().add("custom_field_name", "Mitosis").get()));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void typedExampleShouldContainTypeRestriction() {
+
+ WrapperDocument probe = new WrapperDocument();
+ probe.flatDoc = new FlatDocument();
+ probe.flatDoc.stringValue = "conflux";
+
+ DBObject dbo = mapper.getMappedExample(of(probe, ExampleSpec.typed(WrapperDocument.class)),
+ context.getPersistentEntity(WrapperDocument.class));
+
+ assertThat(dbo,
+ isBsonObject().containing("_class", new BasicDBObject("$in", new String[] { probe.getClass().getName() })));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatchMode() {
+
+ WrapperDocument probe = new WrapperDocument();
+ probe.flatDoc = new FlatDocument();
+ probe.flatDoc.stringValue = "conflux";
+
+ DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(WrapperDocument.class));
+
+ assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "conflux").get()));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictMatchMode() {
+
+ WrapperDocument probe = new WrapperDocument();
+ probe.flatDoc = new FlatDocument();
+ probe.flatDoc.stringValue = "conflux";
+
+ Example> example = Example.of(probe, ExampleSpec.untyped().withIncludeNullValues());
+
+ 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 = Example.of(probe, ExampleSpec.untyped().withStringMatcher(StringMatcher.STARTING));
+
+ 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 exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMatchModeIsStarting() {
+
+ FlatDocument probe = new FlatDocument();
+ probe.stringValue = "fire.ight";
+ probe.intValue = 100;
+
+ Example> example = Example.of(probe, ExampleSpec.untyped().withStringMatcherStarting());
+
+ DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
+
+ assertThat(dbo, is(new BasicDBObjectBuilder()
+ .add("stringValue", new BasicDBObject("$regex", "^" + Pattern.quote("fire.ight"))).add("intValue", 100).get()));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() {
+
+ FlatDocument probe = new FlatDocument();
+ probe.stringValue = "firefight";
+ probe.intValue = 100;
+
+ Example> example = Example.of(probe, ExampleSpec.untyped().withStringMatcher(StringMatcher.ENDING));
+
+ 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 exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() {
+
+ FlatDocument probe = new FlatDocument();
+ probe.stringValue = "firefight";
+ probe.customNamedField = "^(cat|dog).*shelter\\d?";
+
+ Example> example = Example.of(probe, ExampleSpec.untyped().withStringMatcher(StringMatcher.REGEX));
+
+ DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class));
+
+ assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "firefight"))
+ .add("custom_field_name", new BasicDBObject("$regex", "^(cat|dog).*shelter\\d?")).get()));
+ }
+
+ /**
+ * @see DATAMONGO-1245
+ */
+ @Test
+ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() {
+
+ FlatDocument probe = new FlatDocument();
+ probe.stringValue = "firefight";
+ probe.intValue = 100;
+
+ Example> example = Example.of(probe,
+ ExampleSpec.untyped().withStringMatcher(StringMatcher.ENDING).withIgnoreCase());
+
+ 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 = Example.of(probe, ExampleSpec.untyped().withIgnoreCase());
+
+ 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(of(probe), context.getPersistentEntity(WithDBRef.class));
+ com.mongodb.DBRef reference = getTypedValue(dbo, "referenceDocument", com.mongodb.DBRef.class);
+
+ assertThat(reference.getId(), Is.