DATACMNS-1275 - Introduced MappingContext.findPersistentPropertyPaths(Class<?>, Predicate<P>).

MappingContext now exposes a method to detect all property paths pointing to properties matching a given predicate.

Extracted PersistentPropertyPath creation into a dedicated factory class so that it can be tested individually. DefaultPersistentPropertyPath now exposes a ….containsPropertyOfType(…) to detect whether we've already processed a particular type in the path. Also applied a bit of Java 8 and Lombok polish.

InvalidPersistentPropertyPath now collects suggested alternatives to create a better exception message. PersistentEntities now allows to map over a MappingContext and PersistentEntity that a given type is corresponding to. Streamable now exposes an ….isEmpty(). Removed references to equivalent methods implemented in subtypes.
This commit is contained in:
Oliver Gierke
2018-03-06 13:01:01 +01:00
parent 5901a27130
commit ac324b22a5
19 changed files with 1068 additions and 258 deletions

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2018 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.mapping;
import static org.assertj.core.api.Assertions.*;
import lombok.Value;
import org.junit.Test;
import org.springframework.data.mapping.context.SampleMappingContext;
/**
* @author Oliver Gierke
*/
public class PersistentPropertyAccessorUnitTests {
SampleMappingContext context = new SampleMappingContext();
PersistentPropertyPath<? extends PersistentProperty<?>> path;
PersistentPropertyAccessor accessor;
private void setUp(Order order, String path) {
this.accessor = context.getPersistentEntity(Order.class).getPropertyAccessor(order);
this.path = context.getPersistentPropertyPath(path, Order.class);
}
@Test // DATACMNS-1275
public void looksUpValueForPropertyPath() {
Order order = new Order(new Customer("Dave"));
setUp(order, "customer.firstname");
assertThat(accessor.getProperty(path)).isEqualTo("Dave");
}
@Test // DATACMNS-1275
public void setsPropertyOnNestedPath() {
Customer customer = new Customer("Dave");
Order order = new Order(customer);
setUp(order, "customer.firstname");
accessor.setProperty(path, "Oliver August");
assertThat(customer.firstname).isEqualTo("Oliver August");
}
@Test // DATACMNS-1275
public void rejectsEmptyPathToSetValues() {
setUp(new Order(null), "");
assertThatExceptionOfType(IllegalArgumentException.class) //
.isThrownBy(() -> accessor.setProperty(path, "Oliver August"));
}
@Test // DATACMNS-1275
public void rejectsIntermediateNullValuesForRead() {
setUp(new Order(null), "customer.firstname");
assertThatExceptionOfType(MappingException.class)//
.isThrownBy(() -> accessor.getProperty(path));
}
@Test // DATACMNS-1275
public void rejectsIntermediateNullValuesForWrite() {
setUp(new Order(null), "customer.firstname");
assertThatExceptionOfType(MappingException.class)//
.isThrownBy(() -> accessor.setProperty(path, "Oliver August"));
}
@Value
static class Order {
Customer customer;
}
@Value
static class Customer {
String firstname;
}
}

View File

@@ -34,12 +34,10 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.StringUtils;
/**
* Unit test for {@link AbstractMappingContext}.
@@ -54,17 +52,11 @@ public class AbstractMappingContextUnitTests {
@Before
public void setUp() {
context = new SampleMappingContext();
context.setSimpleTypeHolder(new SimpleTypeHolder(Collections.singleton(LocalDateTime.class), true));
}
@Test
public void doesNotTryToLookupPersistentEntityForLeafProperty() {
PersistentPropertyPath<SamplePersistentProperty> path = context
.getPersistentPropertyPath(PropertyPath.from("name", Person.class));
assertThat(path).isNotNull();
}
@Test(expected = MappingException.class) // DATACMNS-92
public void doesNotAddInvalidEntity() {
@@ -158,22 +150,6 @@ public class AbstractMappingContextUnitTests {
.satisfies(inner -> assertThat(((PersistentEntity) inner).getType()).isEqualTo(Person.class)));
}
@Test // DATACMNS-380
public void returnsPersistentPropertyPathForDotPath() {
PersistentPropertyPath<SamplePersistentProperty> path = context.getPersistentPropertyPath("persons.name",
Sample.class);
assertThat(path.getLength()).isEqualTo(2);
assertThat(path.getBaseProperty().getName()).isEqualTo("persons");
assertThat(path.getLeafProperty().getName()).isEqualTo("name");
}
@Test(expected = MappingException.class) // DATACMNS-380
public void rejectsInvalidPropertyReferenceWithMappingException() {
context.getPersistentPropertyPath("foo", Sample.class);
}
@Test // DATACMNS-390
public void exposesCopyOfPersistentEntitiesToAvoidConcurrentModificationException() {
@@ -220,29 +196,6 @@ public class AbstractMappingContextUnitTests {
assertThat(context.getPersistentEntity(TypeCreatingSyntheticClass.class)).isNotNull();
}
@Test // DATACMNS-695
public void persistentPropertyPathTraversesGenericTypesCorrectly() {
assertThat(context.getPersistentPropertyPath("field.wrapped.field", Outer.class)).hasSize(3);
}
@Test // DATACMNS-727
public void exposesContextForFailingPropertyPathLookup() {
assertThatExceptionOfType(InvalidPersistentPropertyPath.class)//
.isThrownBy(() -> context.getPersistentPropertyPath("persons.firstname", Sample.class))//
.matches(e -> StringUtils.hasText(e.getMessage()))//
.matches(e -> e.getResolvedPath().equals("persons"))//
.matches(e -> e.getUnresolvableSegment().equals("firstname"))//
.matches(e -> context.getPersistentPropertyPath(e) != null);
}
@Test // DATACMNS-1116
public void cachesPersistentPropertyPaths() {
assertThat(context.getPersistentPropertyPath("persons.name", Sample.class)) //
.isSameAs(context.getPersistentPropertyPath("persons.name", Sample.class));
}
@Test // DATACMNS-1208
public void ensureHasPersistentEntityReportsFalseForTypesThatShouldntBeCreated() {
@@ -299,17 +252,4 @@ public class AbstractMappingContextUnitTests {
static class Extension extends Base {
@Id String foo;
}
static class Outer {
Wrapper<Inner> field;
}
static class Wrapper<T> {
T wrapped;
}
static class Inner {
String field;
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.mapping.context;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
@@ -28,6 +29,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPath;
/**
* Unit tests for {@link DefaultPersistentPropertyPath}.
@@ -35,14 +37,14 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultPersistenPropertyPathUnitTests<T extends PersistentProperty<T>> {
public class DefaultPersistentPropertyPathUnitTests<P extends PersistentProperty<P>> {
@Mock T first, second;
@Mock P first, second;
@Mock Converter<T, String> converter;
@Mock Converter<P, String> converter;
PersistentPropertyPath<T> oneLeg;
PersistentPropertyPath<T> twoLegs;
PersistentPropertyPath<P> oneLeg;
PersistentPropertyPath<P> twoLegs;
@Before
public void setUp() {
@@ -96,7 +98,7 @@ public class DefaultPersistenPropertyPathUnitTests<T extends PersistentProperty<
@Test
public void calculatesExtensionCorrectly() {
PersistentPropertyPath<T> extension = twoLegs.getExtensionForBaseOf(oneLeg);
PersistentPropertyPath<P> extension = twoLegs.getExtensionForBaseOf(oneLeg);
assertThat(extension).isEqualTo(new DefaultPersistentPropertyPath<>(Collections.singletonList(second)));
}
@@ -107,8 +109,18 @@ public class DefaultPersistenPropertyPathUnitTests<T extends PersistentProperty<
}
@Test
public void returnsItselfAsParentPathIfSizeOne() {
assertThat(oneLeg.getParentPath()).isEqualTo(oneLeg);
public void returnsEmptyPathForRootLevelProperty() {
assertThat(oneLeg.getParentPath()).isEmpty();
}
@Test
public void returnItselfForEmptyPath() {
PersistentPropertyPath<P> parent = oneLeg.getParentPath();
PersistentPropertyPath<P> parentsParent = parent.getParentPath();
assertThat(parentsParent).isEmpty();
assertThat(parentsParent).isSameAs(parent);
}
@Test

View File

@@ -0,0 +1,209 @@
/*
* Copyright 2018 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.mapping.context;
import static org.assertj.core.api.Assertions.*;
import java.util.List;
import javax.inject.Inject;
import org.junit.Test;
import org.springframework.data.annotation.Reference;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.Streamable;
import org.springframework.util.StringUtils;
/**
* Unit tests for {@link PersistentPropertyPathFactory}.
*
* @author Oliver Gierke
* @soundtrack Cypress Hill - Illusions (Q-Tip Remix, Unreleased & Revamped)
*/
public class PersistentPropertyPathFactoryUnitTests {
PersistentPropertyPathFactory<BasicPersistentEntity<Object, SamplePersistentProperty>, SamplePersistentProperty> factory = //
new PersistentPropertyPathFactory<>(new SampleMappingContext());
@Test
public void doesNotTryToLookupPersistentEntityForLeafProperty() {
assertThat(factory.from(Person.class, "name")).isNotNull();
}
@Test // DATACMNS-380
public void returnsPersistentPropertyPathForDotPath() {
PersistentPropertyPath<SamplePersistentProperty> path = factory.from(PersonSample.class, "persons.name");
assertThat(path.getLength()).isEqualTo(2);
assertThat(path.getBaseProperty().getName()).isEqualTo("persons");
assertThat(path.getLeafProperty().getName()).isEqualTo("name");
}
@Test(expected = MappingException.class) // DATACMNS-380
public void rejectsInvalidPropertyReferenceWithMappingException() {
factory.from(PersonSample.class, "foo");
}
@Test // DATACMNS-695
public void persistentPropertyPathTraversesGenericTypesCorrectly() {
assertThat(factory.from(Outer.class, "field.wrapped.field")).hasSize(3);
}
@Test // DATACMNS-727
public void exposesContextForFailingPropertyPathLookup() {
assertThatExceptionOfType(InvalidPersistentPropertyPath.class)//
.isThrownBy(() -> factory.from(PersonSample.class, "persons.firstname"))//
.matches(e -> StringUtils.hasText(e.getMessage()))//
.matches(e -> e.getResolvedPath().equals("persons"))//
.matches(e -> e.getUnresolvableSegment().equals("firstname"))//
.matches(e -> factory.from(PersonSample.class, e.getResolvedPath()) != null);
}
@Test // DATACMNS-1116
public void cachesPersistentPropertyPaths() {
assertThat(factory.from(PersonSample.class, "persons.name")) //
.isSameAs(factory.from(PersonSample.class, "persons.name"));
}
@Test // DATACMNS-1275
public void findsNestedPropertyByFilter() {
PersistentPropertyPaths<?, SamplePersistentProperty> paths = factory.from(Sample.class,
property -> property.findAnnotation(Inject.class) != null);
assertThat(paths).hasSize(1).anySatisfy(it -> it.toDotPath().equals("inner.annotatedField"));
}
@Test // DATACMNS-1275
public void findsNestedPropertiesByFilter() {
PersistentPropertyPaths<?, SamplePersistentProperty> paths = factory.from(Wrapper.class,
property -> property.findAnnotation(Inject.class) != null);
assertThat(paths).hasSize(2);//
assertThat(paths).element(0).satisfies(it -> it.toDotPath().equals("first.inner.annotatedField"));
assertThat(paths).element(1).satisfies(it -> it.toDotPath().equals("second.inner.annotatedField"));
}
@Test // DATACMNS-1275
public void createsEmptyPropertyPathCorrectly() {
assertThat(factory.from(Wrapper.class, "")).isEmpty();
}
@Test // DATACMNS-1275
public void returnsShortestsPathsFirst() {
Streamable<String> paths = factory.from(First.class, it -> true, it -> true) //
.map(PersistentPropertyPath::toDotPath);
assertThat(paths).containsExactly("third", "second", "third.lastname", "second.firstname");
}
@Test // DATACMNS-1275
public void doesNotTraverseAssociationsByDefault() {
Streamable<String> paths = factory.from(First.class, it -> true) //
.map(PersistentPropertyPath::toDotPath);
assertThat(paths) //
.contains("third", "second", "second.firstname")//
.doesNotContain("third.lastname");
}
@Test // DATACMNS-1275
public void traversesAssociationsIfTraversalGuardAllowsIt() {
PersistentPropertyPaths<First, SamplePersistentProperty> paths = //
factory.from(First.class, it -> true, it -> true);
assertThat(paths.contains("third.lastname")).isTrue();
assertThat(paths.contains(PropertyPath.from("third.lastname", First.class)));
}
@Test // DATACMNS-1275
public void returnsEmptyPropertyPathsIfNoneSelected() {
PersistentPropertyPaths<Third, SamplePersistentProperty> paths = factory.from(Third.class, it -> false);
assertThat(paths).isEmpty();
assertThat(paths.getFirst()).isEmpty();
}
@Test // DATACMNS-1275
public void returnsShortestPathFirst() {
PersistentPropertyPaths<First, SamplePersistentProperty> paths = factory.from(First.class, it -> !it.isEntity(),
it -> true);
assertThat(paths.contains("second.firstname")).isTrue();
assertThat(paths.getFirst()) //
.hasValueSatisfying(it -> assertThat(it.toDotPath()).isEqualTo("third.lastname"));
}
static class PersonSample {
List<Person> persons;
}
class Person {
String name;
}
static class Wrapper {
Sample first;
Sample second;
}
static class Sample {
Inner inner;
}
static class Inner {
@Inject String annotatedField;
Sample cyclic;
}
static class Outer {
Generic<Nested> field;
}
static class Generic<T> {
T wrapped;
}
static class Nested {
String field;
}
static class First {
Second second;
@Reference Third third;
}
static class Second {
String firstname;
}
static class Third {
String lastname;
}
}

View File

@@ -72,7 +72,6 @@ public class AbstractPersistentPropertyUnitTests {
@Test // DATACMNS-101
public void returnsNestedEntityTypeCorrectly() {
assertThat(getProperty(TestClassComplex.class, "testClassSet").getPersistentEntityTypes()).isEmpty();
assertThat(getProperty(TestClassComplex.class, "testClassSet").getPersistentEntityType()).isEmpty();
}
@Test // DATACMNS-132
@@ -198,7 +197,6 @@ public class AbstractPersistentPropertyUnitTests {
SamplePersistentProperty property = getProperty(TreeMapWrapper.class, "map");
assertThat(property.getPersistentEntityTypes()).isEmpty();
assertThat(property.getPersistentEntityType()).isEmpty();
assertThat(property.isEntity()).isFalse();
}