DATACMNS-1639 - Introduce InstantiationAwarePropertyAccessor.

We now ship and use a PropertyAccessor implementation by default that will fall back to use the persistence constructor and creating a new instance to "set" a property. This allows completely immutable types, ommitting previously needed wither methods, e.g. to populate automatically generated identifier values.

This is implemented by using the EntityInstantiator available and a ParameterValueProvider that returns the value of the properties of the current instance but replacing the one for the value to be set. This requires moving EntityInstantiators, the EntityInstantiator SPI as well as all existing implementations (reflective, class generating and the Kotlin one) into the mapping.model package. Legacy instances stay around in deprecated form to avoid breaking existing clients (other store modules as well as user implementations of EntityInstantiator).

The newly introduced implementations stay package protected as their sole users are now colocated in the same package. Access from the legacy types is handled via InternalEntityInstantiatorFactory, which is introduced in deprecated from for removal alongside the actual deprecations.
This commit is contained in:
Oliver Drotbohm
2019-12-17 13:43:51 +01:00
parent 09a7de07ad
commit 1d2b81bec8
25 changed files with 1443 additions and 841 deletions

View File

@@ -1,522 +0,0 @@
/*
* Copyright 2014-2020 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
*
* https://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.convert;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.util.ClassTypeInformation.from;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.classloadersupport.HidingClassLoader;
import org.springframework.data.convert.ClassGeneratingEntityInstantiator.ObjectInstantiator;
import org.springframework.data.convert.ClassGeneratingEntityInstantiatorUnitTests.Outer.Inner;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.MappingInstantiationException;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
import org.springframework.util.ReflectionUtils;
/**
* Unit tests for {@link ClassGeneratingEntityInstantiator}.
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
ClassGeneratingEntityInstantiator instance = new ClassGeneratingEntityInstantiator();
@Mock PersistentEntity<?, P> entity;
@Mock ParameterValueProvider<P> provider;
@Test
public void instantiatesSimpleObjectCorrectly() {
doReturn(Object.class).when(entity).getType();
this.instance.createInstance(entity, provider);
}
@Test
public void instantiatesArrayCorrectly() {
doReturn(String[][].class).when(entity).getType();
this.instance.createInstance(entity, provider);
}
@Test // DATACMNS-1126
public void instantiatesTypeWithPreferredConstructorUsingParameterValueProvider() {
PreferredConstructor<Foo, P> constructor = PreferredConstructorDiscoverer.discover(Foo.class);
doReturn(Foo.class).when(entity).getType();
doReturn(constructor).when(entity).getPersistenceConstructor();
assertThat(instance.createInstance(entity, provider)).isInstanceOf(Foo.class);
assertThat(constructor)
.satisfies(it -> verify(provider, times(1)).getParameterValue(it.getParameters().iterator().next()));
}
@Test // DATACMNS-300, DATACMNS-578
@SuppressWarnings({ "unchecked", "rawtypes" })
public void throwsExceptionOnBeanInstantiationException() {
doReturn(PersistentEntity.class).when(entity).getType();
assertThatExceptionOfType(MappingInstantiationException.class)
.isThrownBy(() -> this.instance.createInstance(entity, provider));
}
@Test // DATACMNS-134, DATACMNS-578
public void createsInnerClassInstanceCorrectly() {
BasicPersistentEntity<Inner, P> entity = new BasicPersistentEntity<>(from(Inner.class));
assertThat(entity.getPersistenceConstructor()).satisfies(constructor -> {
Parameter<Object, P> parameter = constructor.getParameters().iterator().next();
Object outer = new Outer();
doReturn(outer).when(provider).getParameterValue(parameter);
Inner instance = this.instance.createInstance(entity, provider);
assertThat(instance).isNotNull();
// Hack to check synthetic field as compiles create different field names (e.g. this$0, this$1)
ReflectionUtils.doWithFields(Inner.class, field -> {
if (field.isSynthetic() && field.getName().startsWith("this$")) {
ReflectionUtils.makeAccessible(field);
assertThat(ReflectionUtils.getField(field, instance)).isEqualTo(outer);
}
});
});
}
@Test // DATACMNS-283, DATACMNS-578
@SuppressWarnings({ "unchecked", "rawtypes" })
public void capturesContextOnInstantiationException() throws Exception {
PersistentEntity<Sample, P> entity = new BasicPersistentEntity<>(from(Sample.class));
doReturn("FOO").when(provider).getParameterValue(any(Parameter.class));
Constructor constructor = Sample.class.getConstructor(Long.class, String.class);
List<Object> parameters = Arrays.asList("FOO", "FOO");
try {
this.instance.createInstance(entity, provider);
fail("Expected MappingInstantiationException!");
} catch (MappingInstantiationException o_O) {
assertThat(o_O.getConstructor()).hasValue(constructor);
assertThat(o_O.getConstructorArguments()).isEqualTo(parameters);
assertThat(o_O.getEntityType()).hasValue(Sample.class);
assertThat(o_O.getMessage()).contains(Sample.class.getName());
assertThat(o_O.getMessage()).contains(Long.class.getName());
assertThat(o_O.getMessage()).contains(String.class.getName());
assertThat(o_O.getMessage()).contains("FOO");
}
}
@Test // DATACMNS-1175
@SuppressWarnings({ "unchecked", "rawtypes" })
public void createsInstancesWithRecursionAndSameCtorArgCountCorrectly() {
PersistentEntity<SampleWithReference, P> outer = new BasicPersistentEntity<>(from(SampleWithReference.class));
PersistentEntity<Sample, P> inner = new BasicPersistentEntity<>(from(Sample.class));
doReturn(2L, "FOO").when(provider).getParameterValue(any(Parameter.class));
ParameterValueProvider<P> recursive = new ParameterValueProvider<P>() {
@Override
public <T> T getParameterValue(Parameter<T, P> parameter) {
if (parameter.getName().equals("id")) {
return (T) Long.valueOf(1);
}
if (parameter.getName().equals("sample")) {
return (T) instance.createInstance(inner, provider);
}
throw new UnsupportedOperationException(parameter.getName());
}
};
SampleWithReference reference = this.instance.createInstance(outer, recursive);
assertThat(reference.id).isEqualTo(1L);
assertThat(reference.sample).isNotNull();
assertThat(reference.sample.id).isEqualTo(2L);
assertThat(reference.sample.name).isEqualTo("FOO");
}
@Test // DATACMNS-578, DATACMNS-1126
public void instantiateObjCtorDefault() {
doReturn(ObjCtorDefault.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjCtorDefault.class))//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2)
.forEach(i -> assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(ObjCtorDefault.class));
}
@Test // DATACMNS-578, DATACMNS-1126
public void instantiateObjCtorNoArgs() {
doReturn(ObjCtorNoArgs.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjCtorNoArgs.class))//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2).forEach(i -> {
Object instance = this.instance.createInstance(entity, provider);
assertThat(instance).isInstanceOf(ObjCtorNoArgs.class);
assertThat(((ObjCtorNoArgs) instance).ctorInvoked).isTrue();
});
}
@Test // DATACMNS-578, DATACMNS-1126
public void instantiateObjCtor1ParamString() {
doReturn(ObjCtor1ParamString.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjCtor1ParamString.class))//
.when(entity).getPersistenceConstructor();
doReturn("FOO").when(provider).getParameterValue(any());
IntStream.range(0, 2).forEach(i -> {
Object instance = this.instance.createInstance(entity, provider);
assertThat(instance).isInstanceOf(ObjCtor1ParamString.class);
assertThat(((ObjCtor1ParamString) instance).ctorInvoked).isTrue();
assertThat(((ObjCtor1ParamString) instance).param1).isEqualTo("FOO");
});
}
@Test // DATACMNS-578, DATACMNS-1126
public void instantiateObjCtor2ParamStringString() {
doReturn(ObjCtor2ParamStringString.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjCtor2ParamStringString.class))//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2).forEach(i -> {
when(provider.getParameterValue(any())).thenReturn("FOO", "BAR");
Object instance = this.instance.createInstance(entity, provider);
assertThat(instance).isInstanceOf(ObjCtor2ParamStringString.class);
assertThat(((ObjCtor2ParamStringString) instance).ctorInvoked).isTrue();
assertThat(((ObjCtor2ParamStringString) instance).param1).isEqualTo("FOO");
assertThat(((ObjCtor2ParamStringString) instance).param2).isEqualTo("BAR");
});
}
@Test // DATACMNS-578, DATACMNS-1126
public void instantiateObjectCtor1ParamInt() {
doReturn(ObjectCtor1ParamInt.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor1ParamInt.class))//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2).forEach(i -> {
doReturn(42).when(provider).getParameterValue(any());
Object instance = this.instance.createInstance(entity, provider);
assertThat(instance).isInstanceOf(ObjectCtor1ParamInt.class);
assertThat(((ObjectCtor1ParamInt) instance).param1).isEqualTo(42);
});
}
@Test // DATACMNS-1200
public void instantiateObjectCtor1ParamIntWithoutValue() {
doReturn(ObjectCtor1ParamInt.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor1ParamInt.class))//
.when(entity).getPersistenceConstructor();
assertThatThrownBy(() -> this.instance.createInstance(entity, provider)) //
.hasCauseInstanceOf(IllegalArgumentException.class);
}
@Test // DATACMNS-578, DATACMNS-1126
@SuppressWarnings("unchecked")
public void instantiateObjectCtor7ParamsString5IntsString() {
doReturn(ObjectCtor7ParamsString5IntsString.class).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor7ParamsString5IntsString.class))//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2).forEach(i -> {
when(provider.getParameterValue(any(Parameter.class))).thenReturn("A", 1, 2, 3, 4, 5, "B");
Object instance = this.instance.createInstance(entity, provider);
assertThat(instance).isInstanceOf(ObjectCtor7ParamsString5IntsString.class);
ObjectCtor7ParamsString5IntsString toTest = (ObjectCtor7ParamsString5IntsString) instance;
assertThat(toTest.param1).isEqualTo("A");
assertThat(toTest.param2).isEqualTo(1);
assertThat(toTest.param3).isEqualTo(2);
assertThat(toTest.param4).isEqualTo(3);
assertThat(toTest.param5).isEqualTo(4);
assertThat(toTest.param6).isEqualTo(5);
assertThat(toTest.param7).isEqualTo("B");
});
}
@Test // DATACMNS-1373
public void shouldInstantiateProtectedInnerClass() {
prepareMocks(ProtectedInnerClass.class);
assertThat(this.instance.shouldUseReflectionEntityInstantiator(entity)).isFalse();
assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(ProtectedInnerClass.class);
}
@Test // DATACMNS-1373
public void shouldInstantiatePackagePrivateInnerClass() {
prepareMocks(PackagePrivateInnerClass.class);
assertThat(this.instance.shouldUseReflectionEntityInstantiator(entity)).isFalse();
assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(PackagePrivateInnerClass.class);
}
@Test // DATACMNS-1373
public void shouldNotInstantiatePrivateInnerClass() {
prepareMocks(PrivateInnerClass.class);
assertThat(this.instance.shouldUseReflectionEntityInstantiator(entity)).isTrue();
}
@Test // DATACMNS-1373
public void shouldInstantiateClassWithPackagePrivateConstructor() {
prepareMocks(ClassWithPackagePrivateConstructor.class);
assertThat(this.instance.shouldUseReflectionEntityInstantiator(entity)).isFalse();
assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(ClassWithPackagePrivateConstructor.class);
}
@Test // DATACMNS-1373
public void shouldInstantiateClassInDefaultPackage() throws ClassNotFoundException {
Class<?> typeInDefaultPackage = Class.forName("TypeInDefaultPackage");
prepareMocks(typeInDefaultPackage);
assertThat(this.instance.shouldUseReflectionEntityInstantiator(entity)).isFalse();
assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(typeInDefaultPackage);
}
@Test // DATACMNS-1373
public void shouldNotInstantiateClassWithPrivateConstructor() {
prepareMocks(ClassWithPrivateConstructor.class);
assertThat(this.instance.shouldUseReflectionEntityInstantiator(entity)).isTrue();
}
@Test // DATACMNS-1422
public void shouldUseReflectionIfFrameworkTypesNotVisible() throws Exception {
HidingClassLoader classLoader = HidingClassLoader.hide(ObjectInstantiator.class);
classLoader.excludePackage("org.springframework.data.mapping.model");
// require type from different package to meet visibility quirks
Class<?> entityType = classLoader
.loadClass("org.springframework.data.mapping.model.PersistentPropertyAccessorTests$ClassLoaderTest");
prepareMocks(entityType);
assertThat(this.instance.shouldUseReflectionEntityInstantiator(entity)).isTrue();
}
private void prepareMocks(Class<?> type) {
doReturn(type).when(entity).getType();
doReturn(PreferredConstructorDiscoverer.discover(type))//
.when(entity).getPersistenceConstructor();
}
static class Foo {
Foo(String foo) {
}
}
static class Outer {
class Inner {
}
}
static class Sample {
final Long id;
final String name;
public Sample(Long id, String name) {
this.id = id;
this.name = name;
}
}
static class SampleWithReference {
final Long id;
final Sample sample;
public SampleWithReference(Long id, Sample sample) {
this.id = id;
this.sample = sample;
}
}
/**
* @author Thomas Darimont
*/
public static class ObjCtorDefault {}
/**
* @author Thomas Darimont
*/
public static class ObjCtorNoArgs {
public boolean ctorInvoked;
public ObjCtorNoArgs() {
ctorInvoked = true;
}
}
/**
* @author Thomas Darimont
*/
public static class ObjCtor1ParamString {
public boolean ctorInvoked;
public String param1;
public ObjCtor1ParamString(String param1) {
this.param1 = param1;
this.ctorInvoked = true;
}
}
/**
* @author Thomas Darimont
*/
public static class ObjCtor2ParamStringString {
public boolean ctorInvoked;
public String param1;
public String param2;
public ObjCtor2ParamStringString(String param1, String param2) {
this.ctorInvoked = true;
this.param1 = param1;
this.param2 = param2;
}
}
/**
* @author Thomas Darimont
*/
public static class ObjectCtor1ParamInt {
public int param1;
public ObjectCtor1ParamInt(int param1) {
this.param1 = param1;
}
}
/**
* @author Thomas Darimont
*/
public static class ObjectCtor7ParamsString5IntsString {
public String param1;
public int param2;
public int param3;
public int param4;
public int param5;
public int param6;
public String param7;
public ObjectCtor7ParamsString5IntsString(String param1, int param2, int param3, int param4, int param5, int param6,
String param7) {
this.param1 = param1;
this.param2 = param2;
this.param3 = param3;
this.param4 = param4;
this.param5 = param5;
this.param6 = param6;
this.param7 = param7;
}
}
protected static class ProtectedInnerClass {}
static class PackagePrivateInnerClass {}
private static class PrivateInnerClass {}
static class ClassWithPrivateConstructor {
private ClassWithPrivateConstructor() {}
}
static class ClassWithPackagePrivateConstructor {
ClassWithPackagePrivateConstructor() {}
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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.convert;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.mapping.PersistentEntity;
/**
* Unit tests for {@link EntityInstantiators}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class EntityInstantiatorsUnitTests {
@Mock PersistentEntity<?, ?> entity;
@Mock EntityInstantiator customInstantiator;
@Test
public void rejectsNullFallbackInstantiator() {
assertThatIllegalArgumentException().isThrownBy(() -> new EntityInstantiators((EntityInstantiator) null));
}
@Test
public void usesReflectionEntityInstantiatorAsDefaultFallback() {
EntityInstantiators instantiators = new EntityInstantiators();
assertThat(instantiators.getInstantiatorFor(entity)).isInstanceOf(ClassGeneratingEntityInstantiator.class);
}
@Test
public void returnsCustomInstantiatorForTypeIfRegistered() {
doReturn(String.class).when(entity).getType();
Map<Class<?>, EntityInstantiator> customInstantiators = Collections
.singletonMap(String.class, customInstantiator);
EntityInstantiators instantiators = new EntityInstantiators(customInstantiators);
assertThat(instantiators.getInstantiatorFor(entity)).isEqualTo(customInstantiator);
}
@Test
public void usesCustomFallbackInstantiatorsIfConfigured() {
doReturn(Object.class).when(entity).getType();
Map<Class<?>, EntityInstantiator> customInstantiators = Collections
.singletonMap(String.class, ReflectionEntityInstantiator.INSTANCE);
EntityInstantiators instantiators = new EntityInstantiators(customInstantiator, customInstantiators);
instantiators.getInstantiatorFor(entity);
assertThat(instantiators.getInstantiatorFor(entity)).isEqualTo(customInstantiator);
doReturn(String.class).when(entity).getType();
assertThat(instantiators.getInstantiatorFor(entity))
.isEqualTo((EntityInstantiator) ReflectionEntityInstantiator.INSTANCE);
}
}

View File

@@ -1,152 +0,0 @@
/*
* Copyright 2018-2020 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
*
* https://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.convert;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.mapping.context.SamplePersistentProperty;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.With32Args;
import org.springframework.data.mapping.model.With33Args;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Unit test to verify correct object instantiation using Kotlin defaulting via {@link KotlinClassGeneratingEntityInstantiator}.
*
* @author Mark Paluch
*/
@RunWith(Parameterized.class)
public class ParameterizedKotlinInstantiatorUnitTests {
private final String valueToSet = "THE VALUE";
private final PersistentEntity<Object, SamplePersistentProperty> entity;
private final int propertyCount;
private final int propertyUnderTestIndex;
private final String propertyUnderTestName;
private final EntityInstantiator entityInstantiator;
public ParameterizedKotlinInstantiatorUnitTests(PersistentEntity<Object, SamplePersistentProperty> entity, int propertyCount, int propertyUnderTestIndex, String propertyUnderTestName, EntityInstantiator entityInstantiator, String label) {
this.entity = entity;
this.propertyCount = propertyCount;
this.propertyUnderTestIndex = propertyUnderTestIndex;
this.propertyUnderTestName = propertyUnderTestName;
this.entityInstantiator = entityInstantiator;
}
@Parameters(name = "{5}")
public static List<Object[]> parameters() {
SampleMappingContext context = new SampleMappingContext();
KotlinClassGeneratingEntityInstantiator generatingInstantiator = new KotlinClassGeneratingEntityInstantiator();
ReflectionEntityInstantiator reflectionInstantiator = ReflectionEntityInstantiator.INSTANCE;
List<Object[]> fixtures = new ArrayList<>();
fixtures.addAll(createFixture(context, With32Args.class, 32, generatingInstantiator));
fixtures.addAll(createFixture(context, With32Args.class, 32, reflectionInstantiator));
fixtures.addAll(createFixture(context, With33Args.class, 33, generatingInstantiator));
fixtures.addAll(createFixture(context, With33Args.class, 33, reflectionInstantiator));
return fixtures;
}
private static List<Object[]> createFixture(SampleMappingContext context, Class<?> entityType, int propertyCount, EntityInstantiator entityInstantiator) {
BasicPersistentEntity<Object, SamplePersistentProperty> persistentEntity = context.getPersistentEntity(entityType);
return IntStream.range(0, propertyCount).mapToObj(i -> {
return new Object[]{persistentEntity, propertyCount, i, Integer.toString(i), entityInstantiator, String.format("Property %d for %s using %s", i, entityType.getSimpleName(), entityInstantiator.getClass().getSimpleName())};
}).collect(Collectors.toList());
}
@Test // DATACMNS-1402
public void shouldCreateInstanceWithSinglePropertySet() {
Object instance = entityInstantiator.createInstance(entity, new SingleParameterValueProvider());
for (int i = 0; i < propertyCount; i++) {
Object value = ReflectionTestUtils.getField(instance, Integer.toString(i));
if (propertyUnderTestIndex == i) {
assertThat(value).describedAs("Property " + i + " of " + entity).isEqualTo(valueToSet);
} else {
assertThat(value).describedAs("Property " + i + " of " + entity).isEqualTo("");
}
}
}
@Test // DATACMNS-1402
public void shouldCreateInstanceWithAllExceptSinglePropertySet() {
Object instance = entityInstantiator.createInstance(entity, new AllButParameterValueProvider());
for (int i = 0; i < propertyCount; i++) {
Object value = ReflectionTestUtils.getField(instance, Integer.toString(i));
if (propertyUnderTestIndex == i) {
assertThat(value).describedAs("Property " + i + " of " + entity).isEqualTo("");
} else {
assertThat(value).describedAs("Property " + i + " of " + entity).isEqualTo(Integer.toString(i));
}
}
}
/**
* Return the value to set for the property to test.
*/
class SingleParameterValueProvider implements ParameterValueProvider<SamplePersistentProperty> {
@Override
public <T> T getParameterValue(Parameter<T, SamplePersistentProperty> parameter) {
if (parameter.getName().equals(propertyUnderTestName)) {
return (T) valueToSet;
}
return null;
}
}
/**
* Return the property name as value for all properties except the one to test.
*/
class AllButParameterValueProvider implements ParameterValueProvider<SamplePersistentProperty> {
@Override
public <T> T getParameterValue(Parameter<T, SamplePersistentProperty> parameter) {
if (!parameter.getName().equals(propertyUnderTestName)) {
return (T) parameter.getName();
}
return null;
}
}
}

View File

@@ -1,172 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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.convert;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.convert.ReflectionEntityInstantiator.*;
import static org.springframework.data.util.ClassTypeInformation.from;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.convert.ReflectionEntityInstantiatorUnitTests.Outer.Inner;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.MappingInstantiationException;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
import org.springframework.util.ReflectionUtils;
/**
* Unit tests for {@link ReflectionEntityInstantiator}.
*
* @author Oliver Gierke
* @author Johannes Mockenhaupt
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class ReflectionEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
@Mock PersistentEntity<?, P> entity;
@Mock ParameterValueProvider<P> provider;
@Test
public void instantiatesSimpleObjectCorrectly() {
doReturn(Object.class).when(entity).getType();
INSTANCE.createInstance(entity, provider);
}
@Test
public void instantiatesArrayCorrectly() {
doReturn(String[][].class).when(entity).getType();
INSTANCE.createInstance(entity, provider);
}
@Test // DATACMNS-1126
public void instantiatesTypeWithPreferredConstructorUsingParameterValueProvider() {
PreferredConstructor<Foo, P> constructor = PreferredConstructorDiscoverer.discover(Foo.class);
doReturn(constructor).when(entity).getPersistenceConstructor();
Object instance = INSTANCE.createInstance(entity, provider);
assertThat(instance).isInstanceOf(Foo.class);
assertThat(constructor)
.satisfies(it -> verify(provider, times(1)).getParameterValue(it.getParameters().iterator().next()));
}
@Test // DATACMNS-300
@SuppressWarnings({ "unchecked", "rawtypes" })
public void throwsExceptionOnBeanInstantiationException() {
doReturn(PersistentEntity.class).when(entity).getType();
assertThatExceptionOfType(MappingInstantiationException.class)
.isThrownBy(() -> INSTANCE.createInstance(entity, provider));
}
@Test // DATACMNS-134
public void createsInnerClassInstanceCorrectly() {
BasicPersistentEntity<Inner, P> entity = new BasicPersistentEntity<>(from(Inner.class));
assertThat(entity.getPersistenceConstructor()).satisfies(it -> {
Parameter<Object, P> parameter = it.getParameters().iterator().next();
Object outer = new Outer();
when(provider.getParameterValue(parameter)).thenReturn(outer);
Inner instance = INSTANCE.createInstance(entity, provider);
assertThat(instance).isNotNull();
// Hack to check synthetic field as compiles create different field names (e.g. this$0, this$1)
ReflectionUtils.doWithFields(Inner.class, field -> {
if (field.isSynthetic() && field.getName().startsWith("this$")) {
ReflectionUtils.makeAccessible(field);
assertThat(ReflectionUtils.getField(field, instance)).isEqualTo(outer);
}
});
});
}
@Test // DATACMNS-283
@SuppressWarnings({ "unchecked", "rawtypes" })
public void capturesContextOnInstantiationException() throws Exception {
PersistentEntity<Sample, P> entity = new BasicPersistentEntity<>(from(Sample.class));
doReturn("FOO").when(provider).getParameterValue(any(Parameter.class));
Constructor constructor = Sample.class.getConstructor(Long.class, String.class);
List<Object> parameters = Arrays.asList("FOO", "FOO");
try {
INSTANCE.createInstance(entity, provider);
fail("Expected MappingInstantiationException!");
} catch (MappingInstantiationException o_O) {
assertThat(o_O.getConstructor()).hasValue(constructor);
assertThat(o_O.getConstructorArguments()).isEqualTo(parameters);
assertThat(o_O.getEntityType()).hasValue(Sample.class);
assertThat(o_O.getMessage()).contains(Sample.class.getName());
assertThat(o_O.getMessage()).contains(Long.class.getName());
assertThat(o_O.getMessage()).contains(String.class.getName());
assertThat(o_O.getMessage()).contains("FOO");
}
}
static class Foo {
Foo(String foo) {
}
}
static class Outer {
class Inner {
}
}
static class Sample {
final Long id;
final String name;
public Sample(Long id, String name) {
this.id = id;
this.name = name;
}
}
}