Skip to content

Commit baee26e

Browse files
committed
DATACMNS-1180 - Fixed accessor lookup for generic properties.
In AbstractPersistentProperty, we now resolve the potentially generic return and parameter types of getters and setters. To achieve that Property has now been made aware of the actual owning type.
1 parent a13fc23 commit baee26e

File tree

3 files changed

+54
-22
lines changed

3 files changed

+54
-22
lines changed

src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -513,12 +513,13 @@ public PersistentPropertyCreator(E entity, Map<String, PropertyDescriptor> descr
513513
public void doWith(Field field) {
514514

515515
String fieldName = field.getName();
516+
TypeInformation<?> type = entity.getTypeInformation();
516517

517518
ReflectionUtils.makeAccessible(field);
518519

519520
Property property = Optional.ofNullable(descriptors.get(fieldName))//
520-
.map(it -> Property.of(field, it))//
521-
.orElseGet(() -> Property.of(field));
521+
.map(it -> Property.of(type, field, it))//
522+
.orElseGet(() -> Property.of(type, field));
522523

523524
createAndRegisterProperty(property);
524525

@@ -535,7 +536,7 @@ public void addPropertiesForRemainingDescriptors() {
535536

536537
remainingDescriptors.values().stream() //
537538
.filter(Property::supportsStandalone) //
538-
.map(Property::of) //
539+
.map(it -> Property.of(entity.getTypeInformation(), it)) //
539540
.filter(PersistentPropertyFilter.INSTANCE::matches) //
540541
.forEach(this::createAndRegisterProperty);
541542
}

src/main/java/org/springframework/data/mapping/model/Property.java

+18-10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.springframework.data.util.Lazy;
2828
import org.springframework.data.util.Optionals;
29+
import org.springframework.data.util.TypeInformation;
2930
import org.springframework.lang.Nullable;
3031
import org.springframework.util.Assert;
3132

@@ -49,68 +50,75 @@ public class Property {
4950
private final Lazy<String> name;
5051
private final Lazy<String> toString;
5152

52-
private Property(Optional<Field> field, Optional<PropertyDescriptor> descriptor) {
53+
private Property(TypeInformation<?> type, Optional<Field> field, Optional<PropertyDescriptor> descriptor) {
5354

55+
Assert.notNull(type, "Type must not be null!");
5456
Assert.isTrue(Optionals.isAnyPresent(field, descriptor), "Either field or descriptor has to be given!");
5557

5658
this.field = field;
5759
this.descriptor = descriptor;
5860

59-
this.rawType = withFieldOrDescriptor(Field::getType, PropertyDescriptor::getPropertyType);
61+
this.rawType = withFieldOrDescriptor( //
62+
it -> type.getRequiredProperty(it.getName()).getType(), //
63+
it -> type.getRequiredProperty(it.getName()).getType() //
64+
);
6065
this.hashCode = Lazy.of(() -> withFieldOrDescriptor(Object::hashCode));
6166
this.name = Lazy.of(() -> withFieldOrDescriptor(Field::getName, FeatureDescriptor::getName));
6267
this.toString = Lazy.of(() -> withFieldOrDescriptor(Object::toString));
6368

6469
this.getter = descriptor.map(PropertyDescriptor::getReadMethod)//
6570
.filter(it -> getType() != null)//
66-
.filter(it -> getType().isAssignableFrom(it.getReturnType()));
71+
.filter(it -> getType().isAssignableFrom(type.getReturnType(it).getType()));
6772

6873
this.setter = descriptor.map(PropertyDescriptor::getWriteMethod)//
6974
.filter(it -> getType() != null)//
70-
.filter(it -> it.getParameterTypes()[0].isAssignableFrom(getType()));
75+
.filter(it -> type.getParameterTypes(it).get(0).getType().isAssignableFrom(getType()));
7176
}
7277

7378
/**
7479
* Creates a new {@link Property} backed by the given field.
7580
*
81+
* @param type the owning type, must not be {@literal null}.
7682
* @param field must not be {@literal null}.
7783
* @return
7884
*/
79-
public static Property of(Field field) {
85+
public static Property of(TypeInformation<?> type, Field field) {
8086

8187
Assert.notNull(field, "Field must not be null!");
8288

83-
return new Property(Optional.of(field), Optional.empty());
89+
return new Property(type, Optional.of(field), Optional.empty());
8490
}
8591

8692
/**
8793
* Creates a new {@link Property} backed by the given {@link Field} and {@link PropertyDescriptor}.
8894
*
95+
* @param type the owning type, must not be {@literal null}.
8996
* @param field must not be {@literal null}.
9097
* @param descriptor must not be {@literal null}.
9198
* @return
9299
*/
93-
public static Property of(Field field, PropertyDescriptor descriptor) {
100+
public static Property of(TypeInformation<?> type, Field field, PropertyDescriptor descriptor) {
94101

95102
Assert.notNull(field, "Field must not be null!");
96103
Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
97104

98-
return new Property(Optional.of(field), Optional.of(descriptor));
105+
return new Property(type, Optional.of(field), Optional.of(descriptor));
99106
}
100107

101108
/**
102109
* Creates a new {@link Property} for the given {@link PropertyDescriptor}. The creation might fail if the given
103110
* property is not representing a proper property.
104111
*
112+
* @param type the owning type, must not be {@literal null}.
105113
* @param descriptor must not be {@literal null}.
106114
* @return
107115
* @see #supportsStandalone(PropertyDescriptor)
108116
*/
109-
public static Property of(PropertyDescriptor descriptor) {
117+
public static Property of(TypeInformation<?> type, PropertyDescriptor descriptor) {
110118

111119
Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
112120

113-
return new Property(Optional.empty(), Optional.of(descriptor));
121+
return new Property(type, Optional.empty(), Optional.of(descriptor));
114122
}
115123

116124
/**

src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java

+32-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

20+
import lombok.Getter;
21+
import lombok.Setter;
22+
2023
import java.beans.IntrospectionException;
2124
import java.beans.Introspector;
2225
import java.beans.PropertyDescriptor;
@@ -36,6 +39,7 @@
3639
import org.springframework.data.mapping.PersistentProperty;
3740
import org.springframework.data.mapping.Person;
3841
import org.springframework.data.util.ClassTypeInformation;
42+
import org.springframework.data.util.Optionals;
3943
import org.springframework.data.util.TypeInformation;
4044
import org.springframework.util.ReflectionUtils;
4145

@@ -136,11 +140,13 @@ public void usesCustomSetter() {
136140
public void doesNotDiscoverGetterAndSetterIfNoPropertyDescriptorGiven() {
137141

138142
Field field = ReflectionUtils.findField(AccessorTestClass.class, "id");
139-
PersistentProperty<SamplePersistentProperty> property = new SamplePersistentProperty(Property.of(field),
143+
Property property = Property.of(ClassTypeInformation.from(AccessorTestClass.class), field);
144+
145+
PersistentProperty<SamplePersistentProperty> persistentProperty = new SamplePersistentProperty(property,
140146
getEntity(AccessorTestClass.class), typeHolder);
141147

142-
assertThat(property.getGetter()).isNull();
143-
assertThat(property.getSetter()).isNull();
148+
assertThat(persistentProperty.getGetter()).isNull();
149+
assertThat(persistentProperty.getSetter()).isNull();
144150
}
145151

146152
@Test // DATACMNS-337
@@ -211,21 +217,30 @@ public void resolvesGenericsForRawType() {
211217
assertThat(property.getRawType()).isEqualTo(String.class);
212218
}
213219

220+
@Test // DATACMNS-1180
221+
public void returnsAccessorsForGenericReturnType() {
222+
223+
SamplePersistentProperty property = getProperty(ConcreteGetter.class, "genericField");
224+
225+
assertThat(property.getSetter()).isNotNull();
226+
assertThat(property.getGetter()).isNotNull();
227+
}
228+
214229
private <T> BasicPersistentEntity<T, SamplePersistentProperty> getEntity(Class<T> type) {
215230
return new BasicPersistentEntity<>(ClassTypeInformation.from(type));
216231
}
217232

218233
private <T> SamplePersistentProperty getProperty(Class<T> type, String name) {
219234

235+
TypeInformation<?> typeInformation = ClassTypeInformation.from(type);
220236
Optional<Field> field = Optional.ofNullable(ReflectionUtils.findField(type, name));
221237
Optional<PropertyDescriptor> descriptor = getPropertyDescriptor(type, name);
222238

223-
Property property = field.map(it -> descriptor//
224-
.map(foo -> Property.of(it, foo))//
225-
.orElseGet(() -> Property.of(it))).orElseGet(() -> getPropertyDescriptor(type, name)//
226-
.map(it -> Property.of(it))//
227-
.orElseThrow(
228-
() -> new IllegalArgumentException(String.format("Couldn't find property %s on %s!", name, type))));
239+
Property property = Optionals.firstNonEmpty( //
240+
() -> Optionals.mapIfAllPresent(field, descriptor, (left, right) -> Property.of(typeInformation, left, right)), //
241+
() -> field.map(it -> Property.of(typeInformation, it)), //
242+
() -> descriptor.map(it -> Property.of(typeInformation, it))) //
243+
.orElseThrow(() -> new IllegalArgumentException(String.format("Couldn't find property %s on %s!", name, type)));
229244

230245
return new SamplePersistentProperty(property, getEntity(type), typeHolder);
231246
}
@@ -256,6 +271,14 @@ class SecondConcrete extends Generic<Integer> {
256271

257272
}
258273

274+
@Getter
275+
@Setter
276+
class GenericGetter<T> {
277+
T genericField;
278+
}
279+
280+
class ConcreteGetter extends GenericGetter<String> {}
281+
259282
@SuppressWarnings("serial")
260283
class TestClassSet extends TreeSet<Object> {}
261284

0 commit comments

Comments
 (0)