Browse Source
Introducing UntypedExampleMatcher allows creation of QBE criteria that does not infer a strict type match. Original pull request: #496.pull/691/head
6 changed files with 481 additions and 7 deletions
@ -0,0 +1,234 @@
@@ -0,0 +1,234 @@
|
||||
/* |
||||
* Copyright 2017 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 lombok.EqualsAndHashCode; |
||||
|
||||
import java.util.Set; |
||||
|
||||
import org.springframework.data.domain.ExampleMatcher; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* {@link ExampleMatcher} implementation for query by example (QBE). Unlike plain {@link ExampleMatcher} this untyped |
||||
* counterpart does not enforce a strict type match when executing the query. This allows to use totally unrelated |
||||
* example documents as references for querying collections as long as the used field/property names match. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.0 |
||||
*/ |
||||
@EqualsAndHashCode |
||||
public class UntypedExampleMatcher implements ExampleMatcher { |
||||
|
||||
private final ExampleMatcher delegate; |
||||
|
||||
/** |
||||
* Creates new {@link UntypedExampleMatcher}. |
||||
* |
||||
* @param delegate must not be {@literal null}. |
||||
*/ |
||||
private UntypedExampleMatcher(ExampleMatcher delegate) { |
||||
|
||||
Assert.notNull(delegate, "Delegate must not be null!"); |
||||
this.delegate = delegate; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#matching() |
||||
*/ |
||||
public static UntypedExampleMatcher matching() { |
||||
return new UntypedExampleMatcher(ExampleMatcher.matching()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#matchingAny() |
||||
*/ |
||||
public static UntypedExampleMatcher matchingAny() { |
||||
return new UntypedExampleMatcher(ExampleMatcher.matchingAny()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#matchingAll() |
||||
*/ |
||||
public static UntypedExampleMatcher matchingAll() { |
||||
return new UntypedExampleMatcher(ExampleMatcher.matchingAll()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withIgnorePaths(java.lang.String...) |
||||
*/ |
||||
public UntypedExampleMatcher withIgnorePaths(String... ignoredPaths) { |
||||
return new UntypedExampleMatcher(delegate.withIgnorePaths(ignoredPaths)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withStringMatcher(java.lang.String) |
||||
*/ |
||||
public UntypedExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher) { |
||||
return new UntypedExampleMatcher(delegate.withStringMatcher(defaultStringMatcher)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withIgnoreCase() |
||||
*/ |
||||
public UntypedExampleMatcher withIgnoreCase() { |
||||
return new UntypedExampleMatcher(delegate.withIgnoreCase()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withIgnoreCase(boolean) |
||||
*/ |
||||
public UntypedExampleMatcher withIgnoreCase(boolean defaultIgnoreCase) { |
||||
return new UntypedExampleMatcher(delegate.withIgnoreCase(defaultIgnoreCase)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withMatcher(java.lang.String, org.springframework.data.domain.ExampleMatcher.MatcherConfigurer) |
||||
*/ |
||||
public UntypedExampleMatcher withMatcher(String propertyPath, |
||||
MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) { |
||||
return new UntypedExampleMatcher(delegate.withMatcher(propertyPath, matcherConfigurer)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withMatcher(java.lang.String, org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher) |
||||
*/ |
||||
public UntypedExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher) { |
||||
return new UntypedExampleMatcher(delegate.withMatcher(propertyPath, genericPropertyMatcher)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withTransformer(java.lang.String, org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer) |
||||
*/ |
||||
public UntypedExampleMatcher withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer) { |
||||
return new UntypedExampleMatcher(delegate.withTransformer(propertyPath, propertyValueTransformer)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withIgnoreCase(java.lang.String...) |
||||
*/ |
||||
public UntypedExampleMatcher withIgnoreCase(String... propertyPaths) { |
||||
return new UntypedExampleMatcher(delegate.withIgnoreCase(propertyPaths)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withIncludeNullValues() |
||||
*/ |
||||
public UntypedExampleMatcher withIncludeNullValues() { |
||||
return new UntypedExampleMatcher(delegate.withIncludeNullValues()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withIgnoreNullValues() |
||||
*/ |
||||
public UntypedExampleMatcher withIgnoreNullValues() { |
||||
return new UntypedExampleMatcher(delegate.withIgnoreNullValues()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#withNullHandler(org.springframework.data.domain.ExampleMatcher.NullHandler) |
||||
*/ |
||||
public UntypedExampleMatcher withNullHandler(NullHandler nullHandler) { |
||||
return new UntypedExampleMatcher(delegate.withNullHandler(nullHandler)); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#getNullHandler() |
||||
*/ |
||||
public NullHandler getNullHandler() { |
||||
return delegate.getNullHandler(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#getDefaultStringMatcher() |
||||
*/ |
||||
public StringMatcher getDefaultStringMatcher() { |
||||
return delegate.getDefaultStringMatcher(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#isIgnoreCaseEnabled() |
||||
*/ |
||||
public boolean isIgnoreCaseEnabled() { |
||||
return delegate.isIgnoreCaseEnabled(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#isIgnoredPath() |
||||
*/ |
||||
public boolean isIgnoredPath(String path) { |
||||
return delegate.isIgnoredPath(path); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#getIgnoredPaths() |
||||
*/ |
||||
public Set<String> getIgnoredPaths() { |
||||
return delegate.getIgnoredPaths(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#getPropertySpecifiers() |
||||
*/ |
||||
public PropertySpecifiers getPropertySpecifiers() { |
||||
return delegate.getPropertySpecifiers(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#isAllMatching() |
||||
*/ |
||||
public boolean isAllMatching() { |
||||
return delegate.isAllMatching(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#isAnyMatching() |
||||
*/ |
||||
public boolean isAnyMatching() { |
||||
return delegate.isAnyMatching(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.domain.ExampleMatcher#getMatchMode() |
||||
*/ |
||||
public MatchMode getMatchMode() { |
||||
return delegate.getMatchMode(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,184 @@
@@ -0,0 +1,184 @@
|
||||
/* |
||||
* Copyright 2017 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 static org.assertj.core.api.Assertions.*; |
||||
import static org.hamcrest.Matchers.*; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.data.domain.ExampleMatcher; |
||||
import org.springframework.data.domain.ExampleMatcher.NullHandler; |
||||
import org.springframework.data.domain.ExampleMatcher.StringMatcher; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
*/ |
||||
public class UntypedExampleMatcherUnitTests { |
||||
|
||||
ExampleMatcher matcher; |
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
matcher = UntypedExampleMatcher.matching(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void defaultStringMatcherShouldReturnDefault() { |
||||
assertThat(matcher.getDefaultStringMatcher()).isEqualTo(StringMatcher.DEFAULT); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void ignoreCaseShouldReturnFalseByDefault() { |
||||
assertThat(matcher.isIgnoreCaseEnabled()).isFalse(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void ignoredPathsIsEmptyByDefault() { |
||||
assertThat(matcher.getIgnoredPaths()).isEmpty(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void nullHandlerShouldReturnIgnoreByDefault() { |
||||
assertThat(matcher.getNullHandler()).isEqualTo(NullHandler.IGNORE); |
||||
} |
||||
|
||||
@Test(expected = UnsupportedOperationException.class) // DATAMONGO-1768
|
||||
public void ignoredPathsIsNotModifiable() throws Exception { |
||||
matcher.getIgnoredPaths().add("¯\\_(ツ)_/¯"); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void ignoreCaseShouldReturnTrueWhenIgnoreCaseEnabled() { |
||||
|
||||
matcher = UntypedExampleMatcher.matching().withIgnoreCase(); |
||||
|
||||
assertThat(matcher.isIgnoreCaseEnabled()).isTrue(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void ignoreCaseShouldReturnTrueWhenIgnoreCaseSet() { |
||||
|
||||
matcher = UntypedExampleMatcher.matching().withIgnoreCase(true); |
||||
|
||||
assertThat(matcher.isIgnoreCaseEnabled()).isTrue(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void nullHandlerShouldReturnInclude() throws Exception { |
||||
|
||||
matcher = UntypedExampleMatcher.matching().withIncludeNullValues(); |
||||
|
||||
assertThat(matcher.getNullHandler()).isEqualTo(NullHandler.INCLUDE); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void nullHandlerShouldReturnIgnore() { |
||||
|
||||
matcher = UntypedExampleMatcher.matching().withIgnoreNullValues(); |
||||
|
||||
assertThat(matcher.getNullHandler()).isEqualTo(NullHandler.IGNORE); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void nullHandlerShouldReturnConfiguredValue() { |
||||
|
||||
matcher = UntypedExampleMatcher.matching().withNullHandler(NullHandler.INCLUDE); |
||||
|
||||
assertThat(matcher.getNullHandler()).isEqualTo(NullHandler.INCLUDE); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void ignoredPathsShouldReturnCorrectProperties() { |
||||
|
||||
matcher = UntypedExampleMatcher.matching().withIgnorePaths("foo", "bar", "baz"); |
||||
|
||||
assertThat(matcher.getIgnoredPaths()).contains("foo", "bar", "baz"); |
||||
assertThat(matcher.getIgnoredPaths()).hasSize(3); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void ignoredPathsShouldReturnUniqueProperties() { |
||||
|
||||
matcher = UntypedExampleMatcher.matching().withIgnorePaths("foo", "bar", "foo"); |
||||
|
||||
assertThat(matcher.getIgnoredPaths()).contains("foo", "bar"); |
||||
assertThat(matcher.getIgnoredPaths()).hasSize(2); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void withCreatesNewInstance() { |
||||
|
||||
matcher = UntypedExampleMatcher.matching().withIgnorePaths("foo", "bar", "foo"); |
||||
ExampleMatcher configuredExampleSpec = matcher.withIgnoreCase(); |
||||
|
||||
assertThat(matcher).isNotEqualTo(sameInstance(configuredExampleSpec)); |
||||
assertThat(matcher.getIgnoredPaths()).hasSize(2); |
||||
assertThat(matcher.isIgnoreCaseEnabled()).isFalse(); |
||||
|
||||
assertThat(configuredExampleSpec.getIgnoredPaths()).hasSize(2); |
||||
assertThat(configuredExampleSpec.isIgnoreCaseEnabled()).isTrue(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void defaultMatcherRequiresAllMatching() { |
||||
|
||||
assertThat(UntypedExampleMatcher.matching().isAllMatching()).isTrue(); |
||||
assertThat(UntypedExampleMatcher.matching().isAnyMatching()).isFalse(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void allMatcherRequiresAllMatching() { |
||||
|
||||
assertThat(UntypedExampleMatcher.matchingAll().isAllMatching()).isTrue(); |
||||
assertThat(UntypedExampleMatcher.matchingAll().isAnyMatching()).isFalse(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void anyMatcherYieldsAnyMatching() { |
||||
|
||||
assertThat(UntypedExampleMatcher.matchingAny().isAnyMatching()).isTrue(); |
||||
assertThat(UntypedExampleMatcher.matchingAny().isAllMatching()).isFalse(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void shouldCompareUsingHashCodeAndEquals() { |
||||
|
||||
matcher = UntypedExampleMatcher.matching() //
|
||||
.withIgnorePaths("foo", "bar", "baz") //
|
||||
.withNullHandler(NullHandler.IGNORE) //
|
||||
.withIgnoreCase("ignored-case") //
|
||||
.withMatcher("hello", ExampleMatcher.GenericPropertyMatchers.contains().caseSensitive()) //
|
||||
.withMatcher("world", matcher -> matcher.endsWith()); |
||||
|
||||
ExampleMatcher sameAsMatcher = UntypedExampleMatcher.matching() //
|
||||
.withIgnorePaths("foo", "bar", "baz") //
|
||||
.withNullHandler(NullHandler.IGNORE) //
|
||||
.withIgnoreCase("ignored-case") //
|
||||
.withMatcher("hello", ExampleMatcher.GenericPropertyMatchers.contains().caseSensitive()) //
|
||||
.withMatcher("world", matcher -> matcher.endsWith()); |
||||
|
||||
ExampleMatcher different = UntypedExampleMatcher.matching() //
|
||||
.withIgnorePaths("foo", "bar", "baz") //
|
||||
.withNullHandler(NullHandler.IGNORE) //
|
||||
.withMatcher("hello", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase()); |
||||
|
||||
assertThat(matcher.hashCode()).isEqualTo(sameAsMatcher.hashCode()); |
||||
assertThat(matcher.hashCode()).isNotEqualTo(different.hashCode()); |
||||
assertThat(matcher).isEqualTo(sameAsMatcher); |
||||
assertThat(matcher).isNotEqualTo(different); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue