diff --git a/pom.xml b/pom.xml
index 0710461991..32be87f991 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.dataspring-data-commons
- 1.12.0.BUILD-SNAPSHOT
+ 1.12.0.DATACMNS-810-SNAPSHOTSpring Data Core
diff --git a/src/main/asciidoc/query-by-example.adoc b/src/main/asciidoc/query-by-example.adoc
new file mode 100644
index 0000000000..1c07fb3a38
--- /dev/null
+++ b/src/main/asciidoc/query-by-example.adoc
@@ -0,0 +1,213 @@
+[[query.by.example]]
+== Query by Example
+
+=== Introduction
+
+This chapter will give you an introduction to Query by Example and explain how to use Examples.
+
+Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require to write queries containing field names. In fact, Query by Example does not require to write queries using store-specific query languages at all.
+
+=== Usage
+
+The Query by Example API consists of three parts:
+
+* Probe: That is the actual example of a domain object with populated fields.
+* `ExampleSpec`: The `ExampleSpec` carries details on how to match particular fields. It can be reused across multiple Examples. `ExampleSpec` comes in two flavors: <> and <>.
+* `Example`: An Example consists of the probe and the ExampleSpec. It is used to create the query. An `Example` takes a probe (usually the domain object or a subtype of it) and the `ExampleSpec`.
+
+Query by Example is suited for several use-cases but also comes with limitations:
+
+**When to use**
+
+* Querying your data store with a set of static or dynamic constraints
+* Frequent refactoring of the domain objects without worrying about breaking existing queries
+* Works independently from the underlying data store API
+
+**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
+
+Before getting started with Query by Example, you need to have a domain object. To get started, simply create an interface for your repository:
+
+.Sample Person object
+====
+[source,java]
+----
+public class Person {
+
+ @Id
+ private String id;
+ private String firstname;
+ private String lastname;
+ private Address address;
+
+ // … getters and setters omitted
+}
+----
+====
+
+This is a simple domain object. You can use it to create an `Example`. By default, fields having `null` values are ignored, and strings are matched using the store specific defaults. Examples can be built by either using the `of` factory method or by using <>. `Example` is immutable.
+
+.Simple Example
+====
+[source,java]
+----
+Person person = new Person(); <1>
+
+person.setFirstname("Dave"); <2>
+
+Example example = Example.of(person); <3>
+----
+<1> Create a new instance of the domain object
+<2> Set the properties to query
+<3> Create the `Example`
+====
+
+NOTE: Property names of the sample object must correlate with the property names of the queried domain object.
+
+Examples can be executed ideally with Repositories. To do so, let your repository extend from `QueryByExampleExecutor`, here's an excerpt from the `QueryByExampleExecutor` interface:
+
+.The `QueryByExampleExecutor`
+====
+[source, java]
+----
+public interface QueryByExampleExecutor {
+
+ S findOne(Example example);
+
+ Iterable findAll(Example example);
+
+ // … more functionality omitted.
+}
+----
+====
+
+You can read more about <> below.
+
+[[query.by.example.examplespec]]
+=== Example Spec
+
+Examples are not limited to default settings. You can specify own defaults for string matching, null handling and property-specific settings using the `ExampleSpec`. `ExampleSpec` comes in two flavors: untyped and typed. By default `Example.of(Person.class)` uses an untyped `ExampleSpec`. Using untyped `ExampleSpec` will use the Repository entity information to determine the type to query and has no control over inheritance queries. Also, untyped `ExampleSpec` will use the probe type when using with a Template to determine the type to query. Read more about <> below.
+
+.Untyped Example Spec with customized matching
+====
+[source,java]
+----
+Person person = new Person(); <1>
+
+person.setFirstname("Dave"); <2>
+
+ExampleSpec exampleSpec = ExampleSpec.untyped() <3>
+
+ .withIgnorePaths("lastname") <4>
+
+ .withIncludeNullValues() <5>
+
+ .withStringMatcherEnding(); <6>
+
+Example example = Example.of(person, exampleSpec); <7>
+
+----
+<1> Create a new instance of the domain object.
+<2> Set properties.
+<3> Create an untyped `ExampleSpec`. The `ExampleSpec` is usable at this stage.
+<4> Construct a new `ExampleSpec` to ignore the property path `lastname`.
+<5> Construct a new `ExampleSpec` to ignore the property path `lastname` and to include null values.
+<6> Construct a new `ExampleSpec` 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 `ExampleSpec`.
+====
+
+`ExampleSpec` is immutable. Calls to `with…(…)` return a copy of `ExampleSpec` with the specific setting applied. Intermediate objects can be safely reused. An `ExampleSpec` can be used to create ad-hoc example specs or to be reused across the application as a specification for `Example`. You can use `ExampleSpec` as a template to configure a default behavior for your example spec. You also can derive from it a more specific `ExampleSpec` where you need to customize it.
+
+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
+====
+[source,java]
+----
+ExampleSpec exampleSpec = ExampleSpec.untyped()
+ .withMatcher("firstname", endsWith())
+ .withMatcher("lastname", startsWith().ignoreCase());
+}
+----
+====
+
+Another style to configure matcher options is by using Java 8 lambdas. This approach is a callback that asks the implementor to modify the matcher. It's not required to return the matcher because configuration options are held within the matcher instance.
+
+.Configuring matcher options with lambdas
+====
+[source,java]
+----
+ExampleSpec exampleSpec = ExampleSpec.untyped()
+ .withMatcher("firstname", matcher -> matcher.endsWith())
+ .withMatcher("firstname", matcher -> matcher.startsWith());
+}
+----
+====
+
+Queries created by `Example` use a merged view of the configuration. Default matching settings can be set at `ExampleSpec` level while individual settings can be applied to particular property paths. Settings that are set on `ExampleSpec` are inherited by property path settings unless they are defined explicitly. Settings on a property patch have higher precedence than default settings.
+
+[cols="1,2", options="header"]
+.Scope of `ExampleSpec` settings
+|===
+| Setting
+| Scope
+
+| Null-handling
+| `ExampleSpec`
+
+| String matching
+| `ExampleSpec` and property path
+
+| Ignoring properties
+| Property path
+
+| Case sensitivity
+| `ExampleSpec` and property path
+
+| Value transformation
+| Property path
+
+|===
+
+[[query.by.example.examplespec.typed]]
+==== Typed Example Spec
+You have now seen the usage of untyped `ExampleSpec`. The second flavor of `ExampleSpec` is typed which adds more control over the result type. When executing an `Example` containing a typed `ExampleSpec` the type of the `ExampleSpec` is used as domain type. Control over the domain type is useful in particular when querying along the inheritance hierarchy or the repository contains multiple types within one table/collection/keyspace.
+
+.Sample Person object
+====
+[source,java]
+----
+public class SpecialPerson extends Person {
+
+ // … more functionality omitted.
+}
+----
+====
+
+.Typed Example Spec with customized matching
+====
+[source,java]
+----
+QueryByExampleExecutor personRepository = … ;
+
+Person person = new Person(); <1>
+
+person.setFirstname("Dave"); <2>
+
+ExampleSpec exampleSpec = ExampleSpec.typed(SpecialPerson.class); <3>
+
+Example example = Example.of(person, exampleSpec); <4>
+
+List result = personRepository.findAll(example); <5>
+
+----
+<1> Create a new instance of the domain object.
+<2> Set properties.
+<3> Create a typed `ExampleSpec` for `SpecialPerson` that extends `Person`.
+<4> Construct a new `Example` using the typed `ExampleSpec` and the `Person` probe.
+<5> Run a query to select all instances of `SpecialPerson` in the repository. Note that the result type is the base class `Person`.
+====
+
diff --git a/src/main/java/org/springframework/data/domain/Example.java b/src/main/java/org/springframework/data/domain/Example.java
new file mode 100644
index 0000000000..3fa95c20e6
--- /dev/null
+++ b/src/main/java/org/springframework/data/domain/Example.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.domain;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Support for query by example (QBE). An {@link Example} takes a {@code probe} to define the example. Matching options
+ * and type safety can be tuned using {@link ExampleSpec}. {@link Example} uses {@link ExampleSpec#untyped()}
+ * {@link ExampleSpec} by default.
+ *
+ * This class is immutable.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @param
+ * @since 1.12
+ */
+public class Example {
+
+ private final T probe;
+ private final ExampleSpec exampleSpec;
+
+ /**
+ * Create a new {@link Example} including all non-null properties by default.
+ *
+ * @param probe The probe to use. Must not be {@literal null}.
+ */
+ @SuppressWarnings("unchecked")
+ private Example(T probe) {
+
+ Assert.notNull(probe, "Probe must not be null!");
+
+ this.probe = probe;
+ this.exampleSpec = ExampleSpec.untyped();
+ }
+
+ /**
+ * Create a new {@link Example} including all non-null properties by default.
+ *
+ * @param probe The probe to use. Must not be {@literal null}.
+ * @param exampleSpec The example specification to use. Must not be {@literal null}.
+ */
+ private Example(T probe, ExampleSpec exampleSpec) {
+
+ Assert.notNull(probe, "Probe must not be null!");
+
+ this.probe = probe;
+ this.exampleSpec = exampleSpec;
+ }
+
+ /**
+ * Create a new {@link Example} including all non-null properties by default using an untyped {@link ExampleSpec}.
+ *
+ * @param probe must not be {@literal null}.
+ * @return
+ */
+ public static Example of(T probe) {
+ return new Example(probe);
+ }
+
+ /**
+ * Create a new {@link Example} with a configured untyped {@link ExampleSpec}.
+ *
+ * @param probe must not be {@literal null}.
+ * @param exampleSpec must not be {@literal null}.
+ * @return
+ */
+ public static Example of(T probe, ExampleSpec exampleSpec) {
+ return new Example(probe, exampleSpec);
+ }
+
+ /**
+ * Create a new {@link Example} with a configured {@link TypedExampleSpec}.
+ *
+ * @param probe must not be {@literal null}.
+ * @param exampleSpec must not be {@literal null}.
+ * @return
+ */
+ public static Example of(S probe, TypedExampleSpec exampleSpec) {
+ return new Example(probe, exampleSpec);
+ }
+
+ /**
+ * Get the example used.
+ *
+ * @return never {@literal null}.
+ */
+ public T getProbe() {
+ return probe;
+ }
+
+ /**
+ * Get the {@link ExampleSpec} used.
+ *
+ * @return never {@literal null}.
+ */
+ public ExampleSpec getExampleSpec() {
+ return exampleSpec;
+ }
+
+ /**
+ * Get the actual type for the probe used. This is usually the given class, but the original class in case of a
+ * CGLIB-generated subclass.
+ *
+ * @return
+ * @see ClassUtils#getUserClass(Class)
+ */
+ @SuppressWarnings("unchecked")
+ public Class getProbeType() {
+ return (Class) ClassUtils.getUserClass(probe.getClass());
+ }
+
+ /**
+ * Get the actual result type for the query. The result type can be different when using {@link TypedExampleSpec}.
+ *
+ * @return
+ * @see ClassUtils#getUserClass(Class)
+ */
+ @SuppressWarnings("unchecked")
+ public Class getResultType() {
+ return (Class) (exampleSpec instanceof TypedExampleSpec> ? ((TypedExampleSpec) exampleSpec).getType()
+ : getProbeType());
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/domain/ExampleSpec.java b/src/main/java/org/springframework/data/domain/ExampleSpec.java
new file mode 100644
index 0000000000..9d8051fabc
--- /dev/null
+++ b/src/main/java/org/springframework/data/domain/ExampleSpec.java
@@ -0,0 +1,845 @@
+/*
+ * Copyright 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.domain;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.util.Assert;
+
+/**
+ * Specification for property path matching to use in query by example (QBE). An {@link ExampleSpec} can be created for
+ * a {@link Class object type}. Instances of {@link ExampleSpec} can be either {@link #untyped()} or
+ * {@link #typed(Class)} and settings can be tuned {@code with...} methods in a fluent style. {@code with...} methods
+ * return a copy of the {@link ExampleSpec} instance with the specified setting. Null-handling defaults to
+ * {@link NullHandler#IGNORE} and case-sensitive {@link StringMatcher#DEFAULT} string matching.
+ *
+ * This class is immutable.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @param
+ * @since 1.12
+ */
+public class ExampleSpec {
+
+ protected final NullHandler nullHandler;
+ protected final StringMatcher defaultStringMatcher;
+ protected final boolean defaultIgnoreCase;
+ protected final PropertySpecifiers propertySpecifiers;
+ protected final Set ignoredPaths;
+
+ ExampleSpec() {
+
+ this.nullHandler = NullHandler.IGNORE;
+ this.defaultStringMatcher = StringMatcher.DEFAULT;
+ this.propertySpecifiers = new PropertySpecifiers();
+ this.defaultIgnoreCase = false;
+ this.ignoredPaths = Collections.emptySet();
+ }
+
+ ExampleSpec(NullHandler nullHandler, StringMatcher defaultStringMatcher, PropertySpecifiers propertySpecifiers,
+ Set ignoredPaths, boolean defaultIgnoreCase) {
+
+ this.nullHandler = nullHandler;
+ this.defaultStringMatcher = defaultStringMatcher;
+ this.propertySpecifiers = propertySpecifiers;
+ this.ignoredPaths = Collections.unmodifiableSet(ignoredPaths);
+ this.defaultIgnoreCase = defaultIgnoreCase;
+ }
+
+ /**
+ * Create a new untyped {@link ExampleSpec} including all non-null properties by default.
+ *
+ * @param type must not be {@literal null}.
+ * @return
+ */
+ public static ExampleSpec untyped() {
+ return new ExampleSpec();
+ }
+
+ /**
+ * Create a new {@link TypedExampleSpec} including all non-null properties by default.
+ *
+ * @param type must not be {@literal null}.
+ * @return
+ */
+ public static TypedExampleSpec typed(Class type) {
+ return new TypedExampleSpec(type);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified {@code propertyPaths}. This instance is immutable and
+ * unaffected by this method call.
+ *
+ * @param ignoredPaths must not be {@literal null} and not empty.
+ * @return
+ */
+ public ExampleSpec withIgnorePaths(String... ignoredPaths) {
+
+ Assert.notEmpty(ignoredPaths, "IgnoredPaths must not be empty!");
+ Assert.noNullElements(ignoredPaths, "IgnoredPaths must not contain null elements!");
+
+ Set newIgnoredPaths = new LinkedHashSet(this.ignoredPaths);
+ newIgnoredPaths.addAll(Arrays.asList(ignoredPaths));
+
+ return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, newIgnoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified string matching of {@link StringMatcher#STARTING}.
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @return
+ */
+ public ExampleSpec withStringMatcherStarting() {
+ return new ExampleSpec(nullHandler, StringMatcher.STARTING, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified string matching of {@link StringMatcher#ENDING}. This
+ * instance is immutable and unaffected by this method call.
+ *
+ * @return
+ */
+ public ExampleSpec withStringMatcherEnding() {
+ return new ExampleSpec(nullHandler, StringMatcher.ENDING, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified string matching of {@link StringMatcher#CONTAINING}.
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @return
+ */
+ public ExampleSpec withStringMatcherContaining() {
+ return new ExampleSpec(nullHandler, StringMatcher.CONTAINING, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified string matching of {@code defaultStringMatcher}. This
+ * instance is immutable and unaffected by this method call.
+ *
+ * @param defaultStringMatcher must not be {@literal null}.
+ * @return
+ */
+ public ExampleSpec withStringMatcher(StringMatcher defaultStringMatcher) {
+
+ Assert.notNull(ignoredPaths, "DefaultStringMatcher must not be empty!");
+
+ return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with ignoring case sensitivity by default. This instance is immutable
+ * and unaffected by this method call.
+ *
+ * @return
+ */
+ public ExampleSpec withIgnoreCase() {
+ return withIgnoreCase(true);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with {@code defaultIgnoreCase}. This instance is immutable and
+ * unaffected by this method call.
+ *
+ * @param defaultIgnoreCase
+ * @return
+ */
+ public ExampleSpec withIgnoreCase(boolean defaultIgnoreCase) {
+ return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified {@code GenericPropertyMatcher} for the
+ * {@code propertyPath}. This instance is immutable and unaffected by this method call.
+ *
+ * @param propertyPath must not be {@literal null}.
+ * @param matcherConfigurer callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}.
+ * @return
+ */
+ public ExampleSpec withMatcher(String propertyPath, MatcherConfigurer matcherConfigurer) {
+
+ Assert.hasText(propertyPath, "PropertyPath must not be empty!");
+ Assert.notNull(matcherConfigurer, "MatcherConfigurer must not be empty!");
+
+ GenericPropertyMatcher genericPropertyMatcher = new GenericPropertyMatcher();
+ matcherConfigurer.configureMatcher(genericPropertyMatcher);
+
+ return withMatcher(propertyPath, genericPropertyMatcher);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified {@code GenericPropertyMatcher} for the
+ * {@code propertyPath}. This instance is immutable and unaffected by this method call.
+ *
+ * @param propertyPath must not be {@literal null}.
+ * @param genericPropertyMatcher callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}.
+ * @return
+ */
+ public ExampleSpec withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher) {
+
+ Assert.hasText(propertyPath, "PropertyPath must not be empty!");
+ Assert.notNull(genericPropertyMatcher, "GenericPropertyMatcher must not be empty!");
+
+ PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
+ PropertySpecifier propertySpecifier = new PropertySpecifier(propertyPath);
+
+ if (genericPropertyMatcher.ignoreCase != null) {
+ propertySpecifier = propertySpecifier.withIgnoreCase(genericPropertyMatcher.ignoreCase);
+ }
+
+ if (genericPropertyMatcher.stringMatcher != null) {
+ propertySpecifier = propertySpecifier.withStringMatcher(genericPropertyMatcher.stringMatcher);
+ }
+
+ if (genericPropertyMatcher.valueTransformer != null) {
+ propertySpecifier = propertySpecifier.withValueTransformer(genericPropertyMatcher.valueTransformer);
+ }
+
+ propertySpecifiers.add(propertySpecifier);
+
+ return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified {@code PropertyValueTransformer} for the
+ * {@code propertyPath}.
+ *
+ * @param propertyPath must not be {@literal null}.
+ * @param propertyValueTransformer must not be {@literal null}.
+ * @return
+ */
+ public ExampleSpec withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer) {
+
+ Assert.hasText(propertyPath, "PropertyPath must not be empty!");
+ Assert.notNull(propertyValueTransformer, "PropertyValueTransformer must not be empty!");
+
+ PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
+ PropertySpecifier propertySpecifier = getOrCreatePropertySpecifier(propertyPath, propertySpecifiers);
+
+ propertySpecifiers.add(propertySpecifier.withValueTransformer(propertyValueTransformer));
+
+ return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with ignore case sensitivity for the {@code propertyPaths}. This
+ * instance is immutable and unaffected by this method call.
+ *
+ * @param propertyPaths must not be {@literal null} and not empty.
+ * @return
+ */
+ public ExampleSpec withIgnoreCase(String... propertyPaths) {
+
+ Assert.notEmpty(propertyPaths, "PropertyPaths must not be empty!");
+ Assert.noNullElements(propertyPaths, "PropertyPaths must not contain null elements!");
+
+ PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
+
+ for (String propertyPath : propertyPaths) {
+ PropertySpecifier propertySpecifier = getOrCreatePropertySpecifier(propertyPath, propertySpecifiers);
+ propertySpecifiers.add(propertySpecifier.withIgnoreCase(true));
+ }
+
+ return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ protected PropertySpecifier getOrCreatePropertySpecifier(String propertyPath, PropertySpecifiers propertySpecifiers) {
+
+ if (propertySpecifiers.hasSpecifierForPath(propertyPath)) {
+ return propertySpecifiers.getForPath(propertyPath);
+ }
+
+ return new PropertySpecifier(propertyPath);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with treatment for {@literal null} values of {@link NullHandler#INCLUDE}
+ * . This instance is immutable and unaffected by this method call.
+ *
+ * @return
+ */
+ public ExampleSpec withIncludeNullValues() {
+ return new ExampleSpec(NullHandler.INCLUDE, defaultStringMatcher, propertySpecifiers, ignoredPaths,
+ defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with treatment for {@literal null} values of {@link NullHandler#IGNORE}.
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @return
+ */
+ public ExampleSpec withIgnoreNullValues() {
+ return new ExampleSpec(NullHandler.IGNORE, defaultStringMatcher, propertySpecifiers, ignoredPaths,
+ defaultIgnoreCase);
+ }
+
+ /**
+ * Returns a copy of this {@link ExampleSpec} with the specified {@code nullHandler}. This instance is immutable and
+ * unaffected by this method call.
+ *
+ * @param nullHandler must not be {@literal null}.
+ * @return
+ */
+ public ExampleSpec withNullHandler(NullHandler nullHandler) {
+
+ Assert.notNull(nullHandler, "NullHandler must not be null!");
+ return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
+ }
+
+ /**
+ * Get defined null handling.
+ *
+ * @return never {@literal null}
+ */
+ public ExampleSpec.NullHandler getNullHandler() {
+ return nullHandler;
+ }
+
+ /**
+ * Get defined {@link ExampleSpec.StringMatcher}.
+ *
+ * @return never {@literal null}.
+ */
+ public ExampleSpec.StringMatcher getDefaultStringMatcher() {
+ return defaultStringMatcher;
+ }
+
+ /**
+ * @return {@literal true} if {@link String} should be matched with ignore case option.
+ */
+ public boolean isIgnoreCaseEnabled() {
+ return this.defaultIgnoreCase;
+ }
+
+ /**
+ * @param path
+ * @return return {@literal true} if path was set to be ignored.
+ */
+ public boolean isIgnoredPath(String path) {
+ return this.ignoredPaths.contains(path);
+ }
+
+ /**
+ * @return unmodifiable {@link Set} of ignored paths.
+ */
+ public Set getIgnoredPaths() {
+ return ignoredPaths;
+ }
+
+ /**
+ * @return the {@link PropertySpecifiers} within the {@link ExampleSpec}.
+ */
+ public PropertySpecifiers getPropertySpecifiers() {
+ return propertySpecifiers;
+ }
+
+ /**
+ * Null handling for creating criterion out of an {@link Example}.
+ *
+ * @author Christoph Strobl
+ */
+ public static enum NullHandler {
+
+ INCLUDE, IGNORE
+ }
+
+ /**
+ * Callback to configure a matcher.
+ *
+ * @author Mark Paluch
+ * @param
+ */
+ public static interface MatcherConfigurer {
+ void configureMatcher(T matcher);
+ }
+
+ /**
+ * A generic property matcher that specifies {@link StringMatcher string matching} and case sensitivity.
+ *
+ * @author Mark Paluch
+ */
+ public static class GenericPropertyMatcher {
+
+ StringMatcher stringMatcher = null;
+ Boolean ignoreCase = null;
+ PropertyValueTransformer valueTransformer = NoOpPropertyValueTransformer.INSTANCE;
+
+ /**
+ * Creates an unconfigured {@link GenericPropertyMatcher}.
+ */
+ public GenericPropertyMatcher() {}
+
+ /**
+ * Creates a new {@link GenericPropertyMatcher} with a {@link StringMatcher} and {@code ignoreCase}.
+ *
+ * @param stringMatcher must not be {@literal null}.
+ * @param ignoreCase
+ * @return
+ */
+ public static GenericPropertyMatcher of(StringMatcher stringMatcher, boolean ignoreCase) {
+ return new GenericPropertyMatcher().stringMatcher(stringMatcher).ignoreCase(ignoreCase);
+ }
+
+ /**
+ * Creates a new {@link GenericPropertyMatcher} with a {@link StringMatcher} and {@code ignoreCase}.
+ *
+ * @param stringMatcher must not be {@literal null}.
+ * @return
+ */
+ public static GenericPropertyMatcher of(StringMatcher stringMatcher) {
+ return new GenericPropertyMatcher().stringMatcher(stringMatcher);
+ }
+
+ /**
+ * Sets ignores case to {@literal true}.
+ *
+ * @return
+ */
+ public GenericPropertyMatcher ignoreCase() {
+
+ this.ignoreCase = true;
+ return this;
+ }
+
+ /**
+ * Sets ignores case to {@code ignoreCase}.
+ *
+ * @param ignoreCase
+ * @return
+ */
+ public GenericPropertyMatcher ignoreCase(boolean ignoreCase) {
+
+ this.ignoreCase = ignoreCase;
+ return this;
+ }
+
+ /**
+ * Sets ignores case to {@literal false}.
+ *
+ * @return
+ */
+ public GenericPropertyMatcher caseSensitive() {
+
+ this.ignoreCase = false;
+ return this;
+ }
+
+ /**
+ * Sets string matcher to {@link StringMatcher#CONTAINING}.
+ *
+ * @return
+ */
+ public GenericPropertyMatcher contains() {
+
+ this.stringMatcher = StringMatcher.CONTAINING;
+ return this;
+ }
+
+ /**
+ * Sets string matcher to {@link StringMatcher#ENDING}.
+ *
+ * @return
+ */
+ public GenericPropertyMatcher endsWith() {
+
+ this.stringMatcher = StringMatcher.ENDING;
+ return this;
+ }
+
+ /**
+ * Sets string matcher to {@link StringMatcher#STARTING}.
+ *
+ * @return
+ */
+ public GenericPropertyMatcher startsWith() {
+
+ this.stringMatcher = StringMatcher.STARTING;
+ return this;
+ }
+
+ /**
+ * Sets string matcher to {@link StringMatcher#EXACT}.
+ *
+ * @return
+ */
+ public GenericPropertyMatcher exact() {
+
+ this.stringMatcher = StringMatcher.EXACT;
+ return this;
+ }
+
+ /**
+ * Sets string matcher to {@link StringMatcher#DEFAULT}.
+ *
+ * @return
+ */
+ public GenericPropertyMatcher storeDefaultMatching() {
+
+ this.stringMatcher = StringMatcher.DEFAULT;
+ return this;
+ }
+
+ /**
+ * Sets string matcher to {@link StringMatcher#REGEX}.
+ *
+ * @return
+ */
+ public GenericPropertyMatcher regex() {
+
+ this.stringMatcher = StringMatcher.REGEX;
+ return this;
+ }
+
+ /**
+ * Sets string matcher to {@code stringMatcher}.
+ *
+ * @param stringMatcher must not be {@literal null}.
+ * @return
+ */
+ public GenericPropertyMatcher stringMatcher(StringMatcher stringMatcher) {
+
+ Assert.notNull(stringMatcher, "StringMatcher must not be null!");
+ this.stringMatcher = stringMatcher;
+ return this;
+ }
+
+ /**
+ * Sets the {@link PropertyValueTransformer} to {@code propertyValueTransformer}.
+ *
+ * @param propertyValueTransformer must not be {@literal null}.
+ * @return
+ */
+ public GenericPropertyMatcher transform(PropertyValueTransformer propertyValueTransformer) {
+
+ Assert.notNull(propertyValueTransformer, "PropertyValueTransformer must not be null!");
+ this.valueTransformer = propertyValueTransformer;
+ return this;
+ }
+
+ }
+
+ /**
+ * Predefined property matchers to create a {@link GenericPropertyMatcher}.
+ *
+ * @author Mark Paluch
+ */
+ public static class GenericPropertyMatchers {
+
+ /**
+ * Creates a {@link GenericPropertyMatcher} that matches string case insensitive.
+ *
+ * @return
+ */
+ public static GenericPropertyMatcher ignoreCase() {
+ return new GenericPropertyMatcher().ignoreCase();
+ }
+
+ /**
+ * Creates a {@link GenericPropertyMatcher} that matches string case sensitive.
+ *
+ * @return
+ */
+ public static GenericPropertyMatcher caseSensitive() {
+ return new GenericPropertyMatcher().caseSensitive();
+ }
+
+ /**
+ * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#CONTAINING}.
+ *
+ * @return
+ */
+ public static GenericPropertyMatcher contains() {
+ return new GenericPropertyMatcher().contains();
+ }
+
+ /**
+ * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#ENDING}.
+ *
+ * @return
+ */
+ public static GenericPropertyMatcher endsWith() {
+ return new GenericPropertyMatcher().endsWith();
+
+ }
+
+ /**
+ * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#STARTING}.
+ *
+ * @return
+ */
+ public static GenericPropertyMatcher startsWith() {
+ return new GenericPropertyMatcher().startsWith();
+ }
+
+ /**
+ * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#EXACT}.
+ *
+ * @return
+ */
+ public static GenericPropertyMatcher exact() {
+ return new GenericPropertyMatcher().startsWith();
+ }
+
+ /**
+ * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#DEFAULT}.
+ *
+ * @return
+ */
+ public static GenericPropertyMatcher storeDefaultMatching() {
+ return new GenericPropertyMatcher().storeDefaultMatching();
+ }
+
+ /**
+ * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#REGEX}.
+ *
+ * @return
+ */
+ public static GenericPropertyMatcher regex() {
+ return new GenericPropertyMatcher().regex();
+ }
+
+ }
+
+ /**
+ * Match modes for treatment of {@link String} values.
+ *
+ * @author Christoph Strobl
+ */
+ public static enum StringMatcher {
+
+ /**
+ * Store specific default.
+ */
+ DEFAULT,
+ /**
+ * Matches the exact string
+ */
+ EXACT,
+ /**
+ * Matches string starting with pattern
+ */
+ STARTING,
+ /**
+ * Matches string ending with pattern
+ */
+ ENDING,
+ /**
+ * Matches string containing pattern
+ */
+ CONTAINING,
+ /**
+ * Treats strings as regular expression patterns
+ */
+ REGEX;
+
+ }
+
+ /**
+ * Allows to transform the property value before it is used in the query.
+ */
+ public static interface PropertyValueTransformer extends Converter