Browse Source

DATACMNS-879 - ExampleMatcher now allows to define whether all or any match should be used.

Introduced dedicated factory methods for ExampleMatcher so that it exposes whether the predicates built from Example instances have to be fulfilled all or if it's sufficient that any of them matches.
pull/168/head
Oliver Gierke 10 years ago
parent
commit
d41a9cb5c5
  1. 6
      src/main/asciidoc/query-by-example.adoc
  2. 95
      src/main/java/org/springframework/data/domain/ExampleMatcher.java
  3. 31
      src/test/java/org/springframework/data/domain/ExampleMatcherUnitTests.java

6
src/main/asciidoc/query-by-example.adoc

@ -27,7 +27,6 @@ Query by Example is suited for several use-cases but also comes with limitations @@ -27,7 +27,6 @@ Query by Example is suited for several use-cases but also comes with limitations
**Limitations**
* Query predicates are combined using the `AND` keyword
* No support for nested/grouped property constraints like `firstname = ?0 or (firstname = ?1 and lastname = ?2)`
* Only supports starts/contains/ends/regex matching for strings and exact matching for other property types
@ -107,13 +106,15 @@ Example<Person> example = Example.of(person, matcher); <7> @@ -107,13 +106,15 @@ Example<Person> example = Example.of(person, matcher); <7>
----
<1> Create a new instance of the domain object.
<2> Set properties.
<3> Create an `ExampleMatcher` which is usable at this stage even without further configuration.
<3> Create an `ExampleMatcher` to expect all values to match. It's usable at this stage even without further configuration.
<4> Construct a new `ExampleMatcher` to ignore the property path `lastname`.
<5> Construct a new `ExampleMatcher` to ignore the property path `lastname` and to include null values.
<6> Construct a new `ExampleMatcher` to ignore the property path `lastname`, to include null values, and use perform suffix string matching.
<7> Create a new `Example` based on the domain object and the configured `ExampleMatcher`.
====
By default the `ExampleMatcher` will expect all values set on the probe to match. If you want to get results matching any of the predicates defined implicitly, use `ExampleMatcher.matchingAny()`.
You can specify behavior for individual properties (e.g. "firstname" and "lastname", "address.city" for nested properties). You can tune it with matching options and case sensitivity.
.Configuring matcher options
@ -164,4 +165,3 @@ Queries created by `Example` use a merged view of the configuration. Default mat @@ -164,4 +165,3 @@ Queries created by `Example` use a merged view of the configuration. Default mat
| Property path
|===

95
src/main/java/org/springframework/data/domain/ExampleMatcher.java

@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode; @@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.FieldDefaults;
import lombok.experimental.Wither;
import java.util.Arrays;
import java.util.Collection;
@ -43,6 +44,7 @@ import org.springframework.util.Assert; @@ -43,6 +44,7 @@ import org.springframework.util.Assert;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Oliver Gierke
* @param <T>
* @since 1.12
*/
@ -57,19 +59,45 @@ public class ExampleMatcher { @@ -57,19 +59,45 @@ public class ExampleMatcher {
PropertySpecifiers propertySpecifiers;
Set<String> ignoredPaths;
boolean defaultIgnoreCase;
@Wither(AccessLevel.PRIVATE) MatchMode mode;
private ExampleMatcher() {
this(NullHandler.IGNORE, StringMatcher.DEFAULT, new PropertySpecifiers(), Collections.<String> emptySet(), false);
this(NullHandler.IGNORE, StringMatcher.DEFAULT, new PropertySpecifiers(), Collections.<String>emptySet(), false,
MatchMode.ALL);
}
/**
* Create a new untyped {@link ExampleMatcher} including all non-null properties by default.
* Create a new {@link ExampleMatcher} including all non-null properties by default exposing that all resulting
* predicates are supposed to be AND-concatenated.
*
* @param type must not be {@literal null}.
* @param type will never be {@literal null}.
* @return
* @see #matchingAll()
*/
public static ExampleMatcher matching() {
return new ExampleMatcher();
return matchingAll();
}
/**
* Create a new {@link ExampleMatcher} including all non-null properties by default matching any predicate derived
* from the example.
*
* @param type will never be {@literal null}.
* @return
*/
public static ExampleMatcher matchingAny() {
return new ExampleMatcher().withMode(MatchMode.ANY);
}
/**
* Create a new {@link ExampleMatcher} including all non-null properties by default matching all predicates derived
* from the example.
*
* @param type will never be {@literal null}.
* @return
*/
public static ExampleMatcher matchingAll() {
return new ExampleMatcher().withMode(MatchMode.ALL);
}
/**
@ -87,8 +115,8 @@ public class ExampleMatcher { @@ -87,8 +115,8 @@ public class ExampleMatcher {
Set<String> newIgnoredPaths = new LinkedHashSet<String>(this.ignoredPaths);
newIgnoredPaths.addAll(Arrays.asList(ignoredPaths));
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, newIgnoredPaths,
defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, newIgnoredPaths, defaultIgnoreCase,
mode);
}
/**
@ -102,7 +130,8 @@ public class ExampleMatcher { @@ -102,7 +130,8 @@ public class ExampleMatcher {
Assert.notNull(ignoredPaths, "DefaultStringMatcher must not be empty!");
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase,
mode);
}
/**
@ -123,7 +152,8 @@ public class ExampleMatcher { @@ -123,7 +152,8 @@ public class ExampleMatcher {
* @return
*/
public ExampleMatcher withIgnoreCase(boolean defaultIgnoreCase) {
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase,
mode);
}
/**
@ -175,7 +205,8 @@ public class ExampleMatcher { @@ -175,7 +205,8 @@ public class ExampleMatcher {
propertySpecifiers.add(propertySpecifier);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase,
mode);
}
/**
@ -196,7 +227,8 @@ public class ExampleMatcher { @@ -196,7 +227,8 @@ public class ExampleMatcher {
propertySpecifiers.add(propertySpecifier.withValueTransformer(propertyValueTransformer));
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase,
mode);
}
/**
@ -218,7 +250,8 @@ public class ExampleMatcher { @@ -218,7 +250,8 @@ public class ExampleMatcher {
propertySpecifiers.add(propertySpecifier.withIgnoreCase(true));
}
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase,
mode);
}
private PropertySpecifier getOrCreatePropertySpecifier(String propertyPath, PropertySpecifiers propertySpecifiers) {
@ -237,8 +270,7 @@ public class ExampleMatcher { @@ -237,8 +270,7 @@ public class ExampleMatcher {
* @return
*/
public ExampleMatcher withIncludeNullValues() {
return new ExampleMatcher(NullHandler.INCLUDE, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
return withNullHandler(NullHandler.INCLUDE);
}
/**
@ -248,8 +280,7 @@ public class ExampleMatcher { @@ -248,8 +280,7 @@ public class ExampleMatcher {
* @return
*/
public ExampleMatcher withIgnoreNullValues() {
return new ExampleMatcher(NullHandler.IGNORE, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
return withNullHandler(NullHandler.IGNORE);
}
/**
@ -262,7 +293,8 @@ public class ExampleMatcher { @@ -262,7 +293,8 @@ public class ExampleMatcher {
public ExampleMatcher withNullHandler(NullHandler nullHandler) {
Assert.notNull(nullHandler, "NullHandler must not be null!");
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase,
mode);
}
/**
@ -312,6 +344,26 @@ public class ExampleMatcher { @@ -312,6 +344,26 @@ public class ExampleMatcher {
return propertySpecifiers;
}
/**
* Returns whether all of the predicates of the {@link Example} are supposed to match. If {@literal false} is
* returned, it's sufficient if any of the predicates derived from the {@link Example} match.
*
* @return whether all of the predicates of the {@link Example} are supposed to match or any of them is sufficient.
*/
public boolean isAllMatching() {
return mode.equals(MatchMode.ALL);
}
/**
* Returns whether it's sufficient that any of the predicates of the {@link Example} match. If {@literal false} is
* returned, all predicates derived from the example need to match to produce results.
*
* @return whether it's sufficient that any of the predicates of the {@link Example} match or all need to match.
*/
public boolean isAnyMatching() {
return mode.equals(MatchMode.ANY);
}
/**
* Null handling for creating criterion out of an {@link Example}.
*
@ -780,4 +832,15 @@ public class ExampleMatcher { @@ -780,4 +832,15 @@ public class ExampleMatcher {
return propertySpecifiers.values();
}
}
/**
* The match modes to expose so that clients can find about how to concatenate the predicates.
*
* @author Oliver Gierke
* @since 1.13
* @see ExampleMatcher#isAllMatching()
*/
private static enum MatchMode {
ALL, ANY;
}
}

31
src/test/java/org/springframework/data/domain/ExampleMatcherUnitTests.java

@ -29,6 +29,7 @@ import org.springframework.data.domain.ExampleMatcher.StringMatcher; @@ -29,6 +29,7 @@ import org.springframework.data.domain.ExampleMatcher.StringMatcher;
* Unit test for {@link ExampleMatcher}.
*
* @author Mark Paluch
* @author Oliver Gierke
* @soundtrack K2 - Der Berg Ruft (Club Mix)
*/
public class ExampleMatcherUnitTests {
@ -176,6 +177,36 @@ public class ExampleMatcherUnitTests { @@ -176,6 +177,36 @@ public class ExampleMatcherUnitTests {
assertThat(configuredExampleSpec.isIgnoreCaseEnabled(), is(true));
}
/**
* @see DATACMNS-879
*/
@Test
public void defaultMatcherRequiresAllMatching() {
assertThat(matching().isAllMatching(), is(true));
assertThat(matching().isAnyMatching(), is(false));
}
/**
* @see DATACMNS-879
*/
@Test
public void allMatcherRequiresAllMatching() {
assertThat(matchingAll().isAllMatching(), is(true));
assertThat(matchingAll().isAnyMatching(), is(false));
}
/**
* @see DATACMNS-879
*/
@Test
public void anyMatcherYieldsAnyMatching() {
assertThat(matchingAny().isAnyMatching(), is(true));
assertThat(matchingAny().isAllMatching(), is(false));
}
static class Person {
String firstname;

Loading…
Cancel
Save