Browse Source
An explorative approach to QBE trying find possibilities and limitations. We now support querying documents by providing a sample of the given object holding compare values. For the sake of partial matching we flatten out nested structures so we can create different queries for matching like:
{ _id : 1, nested : { value : "conflux" } }
{ _id : 1, nested.value : { "conflux" } }
This is useful when you want so search using a only partially filled nested document. String matching can be configured to wrap strings with $regex which creates { firstname : { $regex : "^foo", $options: "i" } } when using StringMatchMode.STARTING along with the ignoreCaseOption. DBRefs and geo structures such as Point or GeoJsonPoint is converted to their according structure.
Related tickets: DATACMNS-810.
Original pull request: #341.
pull/346/merge
23 changed files with 1730 additions and 161 deletions
@ -0,0 +1,229 @@
@@ -0,0 +1,229 @@
|
||||
/* |
||||
* Copyright 2015 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core.convert; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Stack; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.springframework.data.domain.Example; |
||||
import org.springframework.data.domain.Example.NullHandler; |
||||
import org.springframework.data.domain.Example.StringMatcher; |
||||
import org.springframework.data.domain.PropertySpecifier; |
||||
import org.springframework.data.mapping.PropertyHandler; |
||||
import org.springframework.data.mapping.context.MappingContext; |
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; |
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; |
||||
import org.springframework.data.mongodb.core.query.MongoRegexCreator; |
||||
import org.springframework.data.mongodb.core.query.SerializationUtils; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import com.mongodb.BasicDBObject; |
||||
import com.mongodb.DBObject; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @since 1.8 |
||||
*/ |
||||
public class MongoExampleMapper { |
||||
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext; |
||||
private final MongoConverter converter; |
||||
|
||||
public MongoExampleMapper(MongoConverter converter) { |
||||
|
||||
this.converter = converter; |
||||
this.mappingContext = converter.getMappingContext(); |
||||
} |
||||
|
||||
/** |
||||
* Returns the given {@link Example} as {@link DBObject} holding matching values extracted from |
||||
* {@link Example#getProbe()}. |
||||
* |
||||
* @param example |
||||
* @return |
||||
* @since 1.8 |
||||
*/ |
||||
public DBObject getMappedExample(Example<?> example) { |
||||
return getMappedExample(example, mappingContext.getPersistentEntity(example.getSampleType())); |
||||
} |
||||
|
||||
/** |
||||
* Returns the given {@link Example} as {@link DBObject} holding matching values extracted from |
||||
* {@link Example#getProbe()}. |
||||
* |
||||
* @param example |
||||
* @param entity |
||||
* @return |
||||
* @since 1.8 |
||||
*/ |
||||
public DBObject getMappedExample(Example<?> example, MongoPersistentEntity<?> entity) { |
||||
|
||||
DBObject reference = (DBObject) converter.convertToMongoType(example.getSampleObject()); |
||||
|
||||
if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getSampleObject()).getIdentifier() == null) { |
||||
reference.removeField(entity.getIdProperty().getFieldName()); |
||||
} |
||||
|
||||
applyPropertySpecs("", reference, example); |
||||
|
||||
return ObjectUtils.nullSafeEquals(NullHandler.INCLUDE, example.getNullHandler()) ? reference : new BasicDBObject( |
||||
SerializationUtils.flatMap(reference)); |
||||
} |
||||
|
||||
private String getMappedPropertyPath(String path, Example<?> example) { |
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(example.getSampleType()); |
||||
|
||||
Iterator<String> parts = Arrays.asList(path.split("\\.")).iterator(); |
||||
|
||||
final Stack<MongoPersistentProperty> stack = new Stack<MongoPersistentProperty>(); |
||||
|
||||
List<String> resultParts = new ArrayList<String>(); |
||||
|
||||
while (parts.hasNext()) { |
||||
|
||||
final String part = parts.next(); |
||||
MongoPersistentProperty prop = entity.getPersistentProperty(part); |
||||
|
||||
if (prop == null) { |
||||
|
||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() { |
||||
|
||||
@Override |
||||
public void doWithPersistentProperty(MongoPersistentProperty property) { |
||||
|
||||
if (property.getFieldName().equals(part)) { |
||||
stack.push(property); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
if (stack.isEmpty()) { |
||||
return ""; |
||||
} |
||||
prop = stack.pop(); |
||||
} |
||||
|
||||
resultParts.add(prop.getName()); |
||||
|
||||
if (prop.isEntity() && mappingContext.hasPersistentEntityFor(prop.getActualType())) { |
||||
entity = mappingContext.getPersistentEntity(prop.getActualType()); |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return StringUtils.collectionToDelimitedString(resultParts, "."); |
||||
|
||||
} |
||||
|
||||
private void applyPropertySpecs(String path, DBObject source, Example<?> example) { |
||||
|
||||
if (!(source instanceof BasicDBObject)) { |
||||
return; |
||||
} |
||||
|
||||
Iterator<Map.Entry<String, Object>> iter = ((BasicDBObject) source).entrySet().iterator(); |
||||
|
||||
while (iter.hasNext()) { |
||||
|
||||
Map.Entry<String, Object> entry = iter.next(); |
||||
|
||||
if (entry.getKey().equals("_id") && entry.getValue() == null) { |
||||
iter.remove(); |
||||
continue; |
||||
} |
||||
|
||||
String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey(); |
||||
|
||||
String mappedPropertyPath = getMappedPropertyPath(propertyPath, example); |
||||
if (example.isIgnoredPath(propertyPath) || example.isIgnoredPath(mappedPropertyPath)) { |
||||
iter.remove(); |
||||
continue; |
||||
} |
||||
|
||||
PropertySpecifier specifier = null; |
||||
StringMatcher stringMatcher = example.getDefaultStringMatcher(); |
||||
Object value = entry.getValue(); |
||||
boolean ignoreCase = example.isIngnoreCaseEnabled(); |
||||
|
||||
if (example.hasPropertySpecifiers()) { |
||||
|
||||
mappedPropertyPath = example.hasPropertySpecifier(propertyPath) ? propertyPath : getMappedPropertyPath( |
||||
propertyPath, example); |
||||
|
||||
specifier = example.getPropertySpecifier(mappedPropertyPath); |
||||
|
||||
if (specifier != null) { |
||||
if (specifier.hasStringMatcher()) { |
||||
stringMatcher = specifier.getStringMatcher(); |
||||
} |
||||
if (specifier.getIgnoreCase() != null) { |
||||
ignoreCase = specifier.getIgnoreCase(); |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
// TODO: should a PropertySpecifier outrule the later on string matching?
|
||||
if (specifier != null) { |
||||
|
||||
value = specifier.transformValue(value); |
||||
if (value == null) { |
||||
iter.remove(); |
||||
continue; |
||||
} |
||||
|
||||
entry.setValue(value); |
||||
} |
||||
|
||||
if (entry.getValue() instanceof String) { |
||||
applyStringMatcher(entry, stringMatcher, ignoreCase); |
||||
} else if (entry.getValue() instanceof BasicDBObject) { |
||||
applyPropertySpecs(propertyPath, (BasicDBObject) entry.getValue(), example); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void applyStringMatcher(Map.Entry<String, Object> entry, StringMatcher stringMatcher, boolean ignoreCase) { |
||||
|
||||
BasicDBObject dbo = new BasicDBObject(); |
||||
|
||||
if (ObjectUtils.nullSafeEquals(StringMatcher.DEFAULT, stringMatcher)) { |
||||
|
||||
if (ignoreCase) { |
||||
dbo.put("$regex", Pattern.quote((String) entry.getValue())); |
||||
entry.setValue(dbo); |
||||
} |
||||
} else { |
||||
|
||||
String expression = MongoRegexCreator.INSTANCE.toRegularExpression((String) entry.getValue(), |
||||
stringMatcher.getPartType()); |
||||
dbo.put("$regex", expression); |
||||
entry.setValue(dbo); |
||||
} |
||||
|
||||
if (ignoreCase) { |
||||
dbo.put("$options", "i"); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/* |
||||
* Copyright 2015 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core.query; |
||||
|
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.springframework.data.repository.query.parser.Part.Type; |
||||
import org.springframework.util.ObjectUtils; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @since 1.8 |
||||
*/ |
||||
public enum MongoRegexCreator { |
||||
|
||||
INSTANCE; |
||||
|
||||
private static final Pattern PUNCTATION_PATTERN = Pattern.compile("\\p{Punct}"); |
||||
|
||||
/** |
||||
* Creates a regular expression String to be used with {@code $regex}. |
||||
* |
||||
* @param source the plain String |
||||
* @param type |
||||
* @return {@literal source} when {@literal source} or {@literal type} is {@literal null}. |
||||
*/ |
||||
public String toRegularExpression(String source, Type type) { |
||||
|
||||
if (type == null || source == null) { |
||||
return source; |
||||
} |
||||
|
||||
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, type); |
||||
|
||||
switch (type) { |
||||
case STARTING_WITH: |
||||
regex = "^" + regex; |
||||
break; |
||||
case ENDING_WITH: |
||||
regex = regex + "$"; |
||||
break; |
||||
case CONTAINING: |
||||
case NOT_CONTAINING: |
||||
regex = ".*" + regex + ".*"; |
||||
break; |
||||
case SIMPLE_PROPERTY: |
||||
case NEGATING_SIMPLE_PROPERTY: |
||||
regex = "^" + regex + "$"; |
||||
default: |
||||
} |
||||
|
||||
return regex; |
||||
} |
||||
|
||||
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Type type) { |
||||
|
||||
if (!ObjectUtils.nullSafeEquals(Type.LIKE, type)) { |
||||
return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source; |
||||
} |
||||
|
||||
if (source.equals("*")) { |
||||
return ".*"; |
||||
} |
||||
|
||||
StringBuilder sb = new StringBuilder(); |
||||
|
||||
boolean leadingWildcard = source.startsWith("*"); |
||||
boolean trailingWildcard = source.endsWith("*"); |
||||
|
||||
String valueToUse = source.substring(leadingWildcard ? 1 : 0, |
||||
trailingWildcard ? source.length() - 1 : source.length()); |
||||
|
||||
if (PUNCTATION_PATTERN.matcher(valueToUse).find()) { |
||||
valueToUse = Pattern.quote(valueToUse); |
||||
} |
||||
|
||||
if (leadingWildcard) { |
||||
sb.append(".*"); |
||||
} |
||||
sb.append(valueToUse); |
||||
if (trailingWildcard) { |
||||
sb.append(".*"); |
||||
} |
||||
|
||||
return sb.toString(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,449 @@
@@ -0,0 +1,449 @@
|
||||
/* |
||||
* Copyright 2015 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core.convert; |
||||
|
||||
import static org.hamcrest.Matchers.*; |
||||
import static org.junit.Assert.*; |
||||
import static org.springframework.data.domain.Example.*; |
||||
import static org.springframework.data.domain.PropertySpecifier.*; |
||||
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; |
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.hamcrest.core.Is; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.runners.MockitoJUnitRunner; |
||||
import org.springframework.data.annotation.Id; |
||||
import org.springframework.data.domain.Example; |
||||
import org.springframework.data.geo.Point; |
||||
import org.springframework.data.mongodb.MongoDbFactory; |
||||
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.ClassWithGeoTypes; |
||||
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.WithDBRef; |
||||
import org.springframework.data.mongodb.core.mapping.DBRef; |
||||
import org.springframework.data.mongodb.core.mapping.Document; |
||||
import org.springframework.data.mongodb.core.mapping.Field; |
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; |
||||
|
||||
import com.mongodb.BasicDBObject; |
||||
import com.mongodb.BasicDBObjectBuilder; |
||||
import com.mongodb.DBObject; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class MongoExampleMapperUnitTests { |
||||
|
||||
MongoExampleMapper mapper; |
||||
MongoMappingContext context; |
||||
MappingMongoConverter converter; |
||||
|
||||
@Mock MongoDbFactory factory; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
|
||||
this.context = new MongoMappingContext(); |
||||
|
||||
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); |
||||
this.converter.afterPropertiesSet(); |
||||
|
||||
this.mapper = new MongoExampleMapper(converter); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.id = "steelheart"; |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("_id", "steelheart").get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.id = "steelheart"; |
||||
probe.stringValue = "firefight"; |
||||
probe.intValue = 100; |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, |
||||
is(new BasicDBObjectBuilder().add("_id", "steelheart").add("stringValue", "firefight").add("intValue", 100) |
||||
.get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.stringValue = "firefight"; |
||||
probe.intValue = 100; |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "firefight").add("intValue", 100).get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.listOfString = Arrays.asList("Prof", "Tia", "David"); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("listOfString", Arrays.asList("Prof", "Tia", "David")).get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.customNamedField = "Mitosis"; |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("custom_field_name", "Mitosis").get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenienMatchMode() { |
||||
|
||||
WrapperDocument probe = new WrapperDocument(); |
||||
probe.flatDoc = new FlatDocument(); |
||||
probe.flatDoc.stringValue = "conflux"; |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WrapperDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "conflux").get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStriktMatchMode() { |
||||
|
||||
WrapperDocument probe = new WrapperDocument(); |
||||
probe.flatDoc = new FlatDocument(); |
||||
probe.flatDoc.stringValue = "conflux"; |
||||
|
||||
Example<?> example = newExampleOf(probe).includeNullValues().get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); |
||||
|
||||
assertThat(dbo, isBsonObject().containing("flatDoc.stringValue", "conflux")); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.stringValue = "firefight"; |
||||
probe.intValue = 100; |
||||
|
||||
Example<?> example = newExampleOf(probe).matchStringsStartingWith().get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, |
||||
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "^firefight")) |
||||
.add("intValue", 100).get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.stringValue = "firefight"; |
||||
probe.intValue = 100; |
||||
|
||||
Example<?> example = newExampleOf(probe).matchStringsEndingWith().get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, |
||||
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", "firefight$")) |
||||
.add("intValue", 100).get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.stringValue = "firefight"; |
||||
probe.intValue = 100; |
||||
|
||||
Example<?> example = newExampleOf(probe).matchStringsEndingWith().matchStringsWithIgnoreCase().get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat( |
||||
dbo, |
||||
is(new BasicDBObjectBuilder() |
||||
.add("stringValue", new BasicDBObjectBuilder().add("$regex", "firefight$").add("$options", "i").get()) |
||||
.add("intValue", 100).get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.stringValue = "firefight"; |
||||
probe.intValue = 100; |
||||
|
||||
Example<?> example = newExampleOf(probe).matchStringsWithIgnoreCase().get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat( |
||||
dbo, |
||||
is(new BasicDBObjectBuilder() |
||||
.add("stringValue", |
||||
new BasicDBObjectBuilder().add("$regex", Pattern.quote("firefight")).add("$options", "i").get()) |
||||
.add("intValue", 100).get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedWhenContainingDBRef() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.stringValue = "steelheart"; |
||||
probe.referenceDocument = new ReferenceDocument(); |
||||
probe.referenceDocument.id = "200"; |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WithDBRef.class)); |
||||
com.mongodb.DBRef reference = getTypedValue(dbo, "referenceDocument", com.mongodb.DBRef.class); |
||||
|
||||
assertThat(reference.getId(), Is.<Object> is("200")); |
||||
assertThat(reference.getCollectionName(), is("refDoc")); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedWhenDBRefIsNull() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.stringValue = "steelheart"; |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "steelheart").get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { |
||||
|
||||
ClassWithGeoTypes probe = new ClassWithGeoTypes(); |
||||
probe.legacyPoint = new Point(10D, 20D); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(WithDBRef.class)); |
||||
|
||||
assertThat(dbo.get("legacyPoint.x"), Is.<Object> is(10D)); |
||||
assertThat(dbo.get("legacyPoint.y"), Is.<Object> is(20D)); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void mappingShouldExcludeFieldWithCustomNameCorrectly() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.customNamedField = "foo"; |
||||
probe.intValue = 10; |
||||
probe.stringValue = "string"; |
||||
|
||||
Example<?> example = newExampleOf(probe).ignore("customNamedField").get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("stringValue", "string").add("intValue", 10).get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void mappingShouldExcludeFieldCorrectly() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.customNamedField = "foo"; |
||||
probe.intValue = 10; |
||||
probe.stringValue = "string"; |
||||
|
||||
Example<?> example = newExampleOf(probe).ignore("stringValue").get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("custom_field_name", "foo").add("intValue", 10).get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void mappingShouldExcludeNestedFieldCorrectly() { |
||||
|
||||
WrapperDocument probe = new WrapperDocument(); |
||||
probe.flatDoc = new FlatDocument(); |
||||
probe.flatDoc.customNamedField = "foo"; |
||||
probe.flatDoc.intValue = 10; |
||||
probe.flatDoc.stringValue = "string"; |
||||
|
||||
Example<?> example = newExampleOf(probe).ignore("flatDoc.stringValue").get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.custom_field_name", "foo").add("flatDoc.intValue", 10) |
||||
.get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { |
||||
|
||||
WrapperDocument probe = new WrapperDocument(); |
||||
probe.flatDoc = new FlatDocument(); |
||||
probe.flatDoc.customNamedField = "foo"; |
||||
probe.flatDoc.intValue = 10; |
||||
probe.flatDoc.stringValue = "string"; |
||||
|
||||
Example<?> example = newExampleOf(probe).ignore("flatDoc.customNamedField").get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)); |
||||
|
||||
assertThat(dbo, is(new BasicDBObjectBuilder().add("flatDoc.stringValue", "string").add("flatDoc.intValue", 10) |
||||
.get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() { |
||||
|
||||
FlatDocument probe = new FlatDocument(); |
||||
probe.stringValue = "firefight"; |
||||
probe.customNamedField = "steelheart"; |
||||
|
||||
Example<?> example = newExampleOf(probe).specify(newPropertySpecifier("stringValue").matchStringContaining().get()) |
||||
.get(); |
||||
|
||||
DBObject dbo = mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat( |
||||
dbo, |
||||
is(new BasicDBObjectBuilder().add("stringValue", new BasicDBObject("$regex", ".*firefight.*")) |
||||
.add("custom_field_name", "steelheart").get())); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void mappingShouldIncludePropertiesFromHierarchicalDocument() { |
||||
|
||||
HierachicalDocument probe = new HierachicalDocument(); |
||||
probe.stringValue = "firefight"; |
||||
probe.customNamedField = "steelheart"; |
||||
probe.anotherStringValue = "calamity"; |
||||
|
||||
DBObject dbo = mapper.getMappedExample(exampleOf(probe), context.getPersistentEntity(FlatDocument.class)); |
||||
|
||||
assertThat(dbo, isBsonObject().containing("anotherStringValue", "calamity")); |
||||
} |
||||
|
||||
static class FlatDocument { |
||||
|
||||
@Id String id; |
||||
String stringValue; |
||||
@Field("custom_field_name") String customNamedField; |
||||
Integer intValue; |
||||
List<String> listOfString; |
||||
@DBRef ReferenceDocument referenceDocument; |
||||
} |
||||
|
||||
static class HierachicalDocument extends FlatDocument { |
||||
|
||||
String anotherStringValue; |
||||
} |
||||
|
||||
static class WrapperDocument { |
||||
|
||||
@Id String id; |
||||
FlatDocument flatDoc; |
||||
} |
||||
|
||||
@Document(collection = "refDoc") |
||||
static class ReferenceDocument { |
||||
|
||||
@Id String id; |
||||
String value; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,174 @@
@@ -0,0 +1,174 @@
|
||||
/* |
||||
* Copyright 2015 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core.temp; |
||||
|
||||
import java.net.UnknownHostException; |
||||
import java.util.List; |
||||
|
||||
import org.hamcrest.core.Is; |
||||
import org.junit.Assert; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.data.annotation.Id; |
||||
import org.springframework.data.domain.Example; |
||||
import org.springframework.data.mongodb.core.MongoTemplate; |
||||
import org.springframework.data.mongodb.core.mapping.Document; |
||||
import org.springframework.data.mongodb.core.mapping.Field; |
||||
import org.springframework.data.mongodb.core.query.Criteria; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
|
||||
import com.mongodb.MongoClient; |
||||
|
||||
public class QueryByExampleTests { |
||||
|
||||
MongoTemplate template; |
||||
|
||||
@Before |
||||
public void setUp() throws UnknownHostException { |
||||
|
||||
template = new MongoTemplate(new MongoClient(), "query-by-example"); |
||||
template.remove(new Query(), Person.class); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void findByExampleShouldWorkForSimpleProperty() { |
||||
|
||||
init(); |
||||
|
||||
Person sample = new Person(); |
||||
sample.lastname = "stark"; |
||||
|
||||
List<Person> result = template.findByExample(sample); |
||||
Assert.assertThat(result.size(), Is.is(2)); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void findByExampleShouldWorkForMultipleProperties() { |
||||
|
||||
init(); |
||||
|
||||
Person sample = new Person(); |
||||
sample.lastname = "stark"; |
||||
sample.firstname = "arya"; |
||||
|
||||
List<Person> result = template.findByExample(sample); |
||||
Assert.assertThat(result.size(), Is.is(1)); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void findByExampleShouldWorkForIdProperty() { |
||||
|
||||
init(); |
||||
|
||||
Person p4 = new Person(); |
||||
template.save(p4); |
||||
|
||||
Person sample = new Person(); |
||||
sample.id = p4.id; |
||||
|
||||
List<Person> result = template.findByExample(sample); |
||||
Assert.assertThat(result.size(), Is.is(1)); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void findByExampleShouldReturnEmptyListIfNotMatching() { |
||||
|
||||
init(); |
||||
|
||||
Person sample = new Person(); |
||||
sample.firstname = "jon"; |
||||
sample.firstname = "stark"; |
||||
|
||||
List<Person> result = template.findByExample(sample); |
||||
Assert.assertThat(result.size(), Is.is(0)); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void findByExampleShouldReturnEverythingWhenSampleIsEmpty() { |
||||
|
||||
init(); |
||||
|
||||
Person sample = new Person(); |
||||
|
||||
List<Person> result = template.findByExample(sample); |
||||
Assert.assertThat(result.size(), Is.is(3)); |
||||
} |
||||
|
||||
/** |
||||
* @see DATAMONGO-1245 |
||||
*/ |
||||
@Test |
||||
public void findByExampleWithCriteria() { |
||||
|
||||
init(); |
||||
|
||||
Person sample = new Person(); |
||||
sample.lastname = "stark"; |
||||
|
||||
Query query = new Query(new Criteria().alike(new Example<Person>(sample)).and("firstname").regex("^ary*")); |
||||
|
||||
List<Person> result = template.find(query, Person.class); |
||||
Assert.assertThat(result.size(), Is.is(1)); |
||||
} |
||||
|
||||
public void init() { |
||||
|
||||
Person p1 = new Person(); |
||||
p1.firstname = "bran"; |
||||
p1.lastname = "stark"; |
||||
|
||||
Person p2 = new Person(); |
||||
p2.firstname = "jon"; |
||||
p2.lastname = "snow"; |
||||
|
||||
Person p3 = new Person(); |
||||
p3.firstname = "arya"; |
||||
p3.lastname = "stark"; |
||||
|
||||
template.save(p1); |
||||
template.save(p2); |
||||
template.save(p3); |
||||
} |
||||
|
||||
@Document(collection = "dramatis-personae") |
||||
static class Person { |
||||
|
||||
@Id String id; |
||||
String firstname; |
||||
|
||||
@Field("last_name") String lastname; |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Person [id=" + id + ", firstname=" + firstname + ", lastname=" + lastname + "]"; |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue