Skip to content

Commit 2d825be

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-1768 - Introduce UntypedExampleMatcher.
Introducing UntypedExampleMatcher allows creation of QBE criteria that does not infer a strict type match. Original pull request: spring-projects#496.
1 parent 5fedbe9 commit 2d825be

File tree

6 files changed

+481
-7
lines changed

6 files changed

+481
-7
lines changed

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929

3030
import org.bson.Document;
3131
import org.springframework.data.domain.Example;
32-
import org.springframework.data.domain.ExampleMatcher;
3332
import org.springframework.data.domain.ExampleMatcher.NullHandler;
3433
import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer;
3534
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
@@ -40,6 +39,7 @@
4039
import org.springframework.data.mongodb.core.query.MongoRegexCreator;
4140
import org.springframework.data.mongodb.core.query.MongoRegexCreator.MatchMode;
4241
import org.springframework.data.mongodb.core.query.SerializationUtils;
42+
import org.springframework.data.mongodb.core.query.UntypedExampleMatcher;
4343
import org.springframework.data.support.ExampleMatcherAccessor;
4444
import org.springframework.data.util.TypeInformation;
4545
import org.springframework.util.Assert;
@@ -293,7 +293,7 @@ private Document updateTypeRestrictions(Document query, Example example) {
293293

294294
Document result = new Document();
295295

296-
if (isTypeRestricting(example.getMatcher())) {
296+
if (isTypeRestricting(example)) {
297297

298298
result.putAll(query);
299299
this.converter.getTypeMapper().writeTypeRestrictions(result, getTypesToMatch(example));
@@ -309,13 +309,17 @@ private Document updateTypeRestrictions(Document query, Example example) {
309309
return result;
310310
}
311311

312-
private boolean isTypeRestricting(ExampleMatcher matcher) {
312+
private boolean isTypeRestricting(Example example) {
313313

314-
if (matcher.getIgnoredPaths().isEmpty()) {
314+
if (example.getMatcher() instanceof UntypedExampleMatcher) {
315+
return false;
316+
}
317+
318+
if (example.getMatcher().getIgnoredPaths().isEmpty()) {
315319
return true;
316320
}
317321

318-
for (String path : matcher.getIgnoredPaths()) {
322+
for (String path : example.getMatcher().getIgnoredPaths()) {
319323
if (this.converter.getTypeMapper().isTypeKey(path)) {
320324
return false;
321325
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.query;
17+
18+
import lombok.EqualsAndHashCode;
19+
20+
import java.util.Set;
21+
22+
import org.springframework.data.domain.ExampleMatcher;
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* {@link ExampleMatcher} implementation for query by example (QBE). Unlike plain {@link ExampleMatcher} this untyped
27+
* counterpart does not enforce a strict type match when executing the query. This allows to use totally unrelated
28+
* example documents as references for querying collections as long as the used field/property names match.
29+
*
30+
* @author Christoph Strobl
31+
* @since 2.0
32+
*/
33+
@EqualsAndHashCode
34+
public class UntypedExampleMatcher implements ExampleMatcher {
35+
36+
private final ExampleMatcher delegate;
37+
38+
/**
39+
* Creates new {@link UntypedExampleMatcher}.
40+
*
41+
* @param delegate must not be {@literal null}.
42+
*/
43+
private UntypedExampleMatcher(ExampleMatcher delegate) {
44+
45+
Assert.notNull(delegate, "Delegate must not be null!");
46+
this.delegate = delegate;
47+
}
48+
49+
/*
50+
* (non-Javadoc)
51+
* @see org.springframework.data.domain.ExampleMatcher#matching()
52+
*/
53+
public static UntypedExampleMatcher matching() {
54+
return new UntypedExampleMatcher(ExampleMatcher.matching());
55+
}
56+
57+
/*
58+
* (non-Javadoc)
59+
* @see org.springframework.data.domain.ExampleMatcher#matchingAny()
60+
*/
61+
public static UntypedExampleMatcher matchingAny() {
62+
return new UntypedExampleMatcher(ExampleMatcher.matchingAny());
63+
}
64+
65+
/*
66+
* (non-Javadoc)
67+
* @see org.springframework.data.domain.ExampleMatcher#matchingAll()
68+
*/
69+
public static UntypedExampleMatcher matchingAll() {
70+
return new UntypedExampleMatcher(ExampleMatcher.matchingAll());
71+
}
72+
73+
/*
74+
* (non-Javadoc)
75+
* @see org.springframework.data.domain.ExampleMatcher#withIgnorePaths(java.lang.String...)
76+
*/
77+
public UntypedExampleMatcher withIgnorePaths(String... ignoredPaths) {
78+
return new UntypedExampleMatcher(delegate.withIgnorePaths(ignoredPaths));
79+
}
80+
81+
/*
82+
* (non-Javadoc)
83+
* @see org.springframework.data.domain.ExampleMatcher#withStringMatcher(java.lang.String)
84+
*/
85+
public UntypedExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher) {
86+
return new UntypedExampleMatcher(delegate.withStringMatcher(defaultStringMatcher));
87+
}
88+
89+
/*
90+
* (non-Javadoc)
91+
* @see org.springframework.data.domain.ExampleMatcher#withIgnoreCase()
92+
*/
93+
public UntypedExampleMatcher withIgnoreCase() {
94+
return new UntypedExampleMatcher(delegate.withIgnoreCase());
95+
}
96+
97+
/*
98+
* (non-Javadoc)
99+
* @see org.springframework.data.domain.ExampleMatcher#withIgnoreCase(boolean)
100+
*/
101+
public UntypedExampleMatcher withIgnoreCase(boolean defaultIgnoreCase) {
102+
return new UntypedExampleMatcher(delegate.withIgnoreCase(defaultIgnoreCase));
103+
}
104+
105+
/*
106+
* (non-Javadoc)
107+
* @see org.springframework.data.domain.ExampleMatcher#withMatcher(java.lang.String, org.springframework.data.domain.ExampleMatcher.MatcherConfigurer)
108+
*/
109+
public UntypedExampleMatcher withMatcher(String propertyPath,
110+
MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) {
111+
return new UntypedExampleMatcher(delegate.withMatcher(propertyPath, matcherConfigurer));
112+
}
113+
114+
/*
115+
* (non-Javadoc)
116+
* @see org.springframework.data.domain.ExampleMatcher#withMatcher(java.lang.String, org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher)
117+
*/
118+
public UntypedExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher) {
119+
return new UntypedExampleMatcher(delegate.withMatcher(propertyPath, genericPropertyMatcher));
120+
}
121+
122+
/*
123+
* (non-Javadoc)
124+
* @see org.springframework.data.domain.ExampleMatcher#withTransformer(java.lang.String, org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer)
125+
*/
126+
public UntypedExampleMatcher withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer) {
127+
return new UntypedExampleMatcher(delegate.withTransformer(propertyPath, propertyValueTransformer));
128+
}
129+
130+
/*
131+
* (non-Javadoc)
132+
* @see org.springframework.data.domain.ExampleMatcher#withIgnoreCase(java.lang.String...)
133+
*/
134+
public UntypedExampleMatcher withIgnoreCase(String... propertyPaths) {
135+
return new UntypedExampleMatcher(delegate.withIgnoreCase(propertyPaths));
136+
}
137+
138+
/*
139+
* (non-Javadoc)
140+
* @see org.springframework.data.domain.ExampleMatcher#withIncludeNullValues()
141+
*/
142+
public UntypedExampleMatcher withIncludeNullValues() {
143+
return new UntypedExampleMatcher(delegate.withIncludeNullValues());
144+
}
145+
146+
/*
147+
* (non-Javadoc)
148+
* @see org.springframework.data.domain.ExampleMatcher#withIgnoreNullValues()
149+
*/
150+
public UntypedExampleMatcher withIgnoreNullValues() {
151+
return new UntypedExampleMatcher(delegate.withIgnoreNullValues());
152+
}
153+
154+
/*
155+
* (non-Javadoc)
156+
* @see org.springframework.data.domain.ExampleMatcher#withNullHandler(org.springframework.data.domain.ExampleMatcher.NullHandler)
157+
*/
158+
public UntypedExampleMatcher withNullHandler(NullHandler nullHandler) {
159+
return new UntypedExampleMatcher(delegate.withNullHandler(nullHandler));
160+
}
161+
162+
/*
163+
* (non-Javadoc)
164+
* @see org.springframework.data.domain.ExampleMatcher#getNullHandler()
165+
*/
166+
public NullHandler getNullHandler() {
167+
return delegate.getNullHandler();
168+
}
169+
170+
/*
171+
* (non-Javadoc)
172+
* @see org.springframework.data.domain.ExampleMatcher#getDefaultStringMatcher()
173+
*/
174+
public StringMatcher getDefaultStringMatcher() {
175+
return delegate.getDefaultStringMatcher();
176+
}
177+
178+
/*
179+
* (non-Javadoc)
180+
* @see org.springframework.data.domain.ExampleMatcher#isIgnoreCaseEnabled()
181+
*/
182+
public boolean isIgnoreCaseEnabled() {
183+
return delegate.isIgnoreCaseEnabled();
184+
}
185+
186+
/*
187+
* (non-Javadoc)
188+
* @see org.springframework.data.domain.ExampleMatcher#isIgnoredPath()
189+
*/
190+
public boolean isIgnoredPath(String path) {
191+
return delegate.isIgnoredPath(path);
192+
}
193+
194+
/*
195+
* (non-Javadoc)
196+
* @see org.springframework.data.domain.ExampleMatcher#getIgnoredPaths()
197+
*/
198+
public Set<String> getIgnoredPaths() {
199+
return delegate.getIgnoredPaths();
200+
}
201+
202+
/*
203+
* (non-Javadoc)
204+
* @see org.springframework.data.domain.ExampleMatcher#getPropertySpecifiers()
205+
*/
206+
public PropertySpecifiers getPropertySpecifiers() {
207+
return delegate.getPropertySpecifiers();
208+
}
209+
210+
/*
211+
* (non-Javadoc)
212+
* @see org.springframework.data.domain.ExampleMatcher#isAllMatching()
213+
*/
214+
public boolean isAllMatching() {
215+
return delegate.isAllMatching();
216+
}
217+
218+
/*
219+
* (non-Javadoc)
220+
* @see org.springframework.data.domain.ExampleMatcher#isAnyMatching()
221+
*/
222+
public boolean isAnyMatching() {
223+
return delegate.isAnyMatching();
224+
}
225+
226+
/*
227+
* (non-Javadoc)
228+
* @see org.springframework.data.domain.ExampleMatcher#getMatchMode()
229+
*/
230+
public MatchMode getMatchMode() {
231+
return delegate.getMatchMode();
232+
}
233+
234+
}

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.data.mongodb.core.mapping.Field;
3434
import org.springframework.data.mongodb.core.query.Criteria;
3535
import org.springframework.data.mongodb.core.query.Query;
36+
import org.springframework.data.mongodb.core.query.UntypedExampleMatcher;
3637

3738
import com.mongodb.MongoClient;
3839

@@ -179,7 +180,7 @@ public void typedExampleMatchesNothingIfTypesDoNotMatch() {
179180
}
180181

181182
@Test // DATAMONGO-1768
182-
public void untypedExampleMatchesCorrectly() {
183+
public void exampleIgnoringClassTypeKeyMatchesCorrectly() {
183184

184185
NotAPersonButStillMatchingFields probe = new NotAPersonButStillMatchingFields();
185186
probe.lastname = "stark";
@@ -192,6 +193,19 @@ public void untypedExampleMatchesCorrectly() {
192193
assertThat(result, hasItems(p1, p3));
193194
}
194195

196+
@Test // DATAMONGO-1768
197+
public void untypedExampleMatchesCorrectly() {
198+
199+
NotAPersonButStillMatchingFields probe = new NotAPersonButStillMatchingFields();
200+
probe.lastname = "stark";
201+
202+
Query query = new Query(new Criteria().alike(Example.of(probe, UntypedExampleMatcher.matching())));
203+
List<Person> result = operations.find(query, Person.class);
204+
205+
assertThat(result, hasSize(2));
206+
assertThat(result, hasItems(p1, p3));
207+
}
208+
195209
@Document(collection = "dramatis-personae")
196210
@EqualsAndHashCode
197211
@ToString

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.data.mongodb.core.mapping.Document;
4747
import org.springframework.data.mongodb.core.mapping.Field;
4848
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
49+
import org.springframework.data.mongodb.core.query.UntypedExampleMatcher;
4950
import org.springframework.data.mongodb.test.util.IsBsonObject;
5051
import org.springframework.data.util.TypeInformation;
5152

@@ -470,7 +471,7 @@ public void writeTypeRestrictions(org.bson.Document result, Set<Class<?>> restri
470471

471472
@Override
472473
public void writeType(TypeInformation<?> info, Bson sink) {
473-
((org.bson.Document) sink).put("_foo", "bar");
474+
((org.bson.Document) sink).put("_foo", "bar");
474475

475476
}
476477
});
@@ -482,6 +483,17 @@ public void writeType(TypeInformation<?> info, Bson sink) {
482483
assertThat(document, isBsonObject().notContaining("_class").notContaining("_foo"));
483484
}
484485

486+
@Test // DATAMONGO-1768
487+
public void untypedExampleShouldNotInfereTypeRestriction() {
488+
489+
WrapperDocument probe = new WrapperDocument();
490+
probe.flatDoc = new FlatDocument();
491+
probe.flatDoc.stringValue = "conflux";
492+
493+
org.bson.Document document = mapper.getMappedExample(Example.of(probe, UntypedExampleMatcher.matching()));
494+
assertThat(document, isBsonObject().notContaining("_class"));
495+
}
496+
485497
static class FlatDocument {
486498

487499
@Id String id;

0 commit comments

Comments
 (0)