Introduce support to create domain objects via factory methods.
Issue #2476.
This commit is contained in:
committed by
Oliver Drotbohm
parent
0ee9c55f05
commit
c4a324e3cf
@@ -24,7 +24,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.PreferredConstructorDiscovererUnitTests.Outer.Inner;
|
||||
import org.springframework.data.mapping.model.BasicPersistentEntity;
|
||||
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
|
||||
@@ -100,7 +99,7 @@ class PreferredConstructorDiscovererUnitTests<P extends PersistentProperty<P>> {
|
||||
assertThat(PreferredConstructorDiscoverer.discover(entity)).satisfies(constructor -> {
|
||||
|
||||
Parameter<?, P> parameter = constructor.getParameters().iterator().next();
|
||||
assertThat(constructor.isEnclosingClassParameter(parameter)).isTrue();
|
||||
assertThat(constructor.isParentParameter(parameter)).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,12 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.data.annotation.FactoryMethod;
|
||||
import org.springframework.data.classloadersupport.HidingClassLoader;
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
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.ClassGeneratingEntityInstantiator.ObjectInstantiator;
|
||||
import org.springframework.data.mapping.model.ClassGeneratingEntityInstantiatorUnitTests.Outer.Inner;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
@@ -56,7 +57,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
|
||||
|
||||
ClassGeneratingEntityInstantiator instance = new ClassGeneratingEntityInstantiator();
|
||||
ClassGeneratingEntityInstantiator instance = new ClassGeneratingEntityInstantiator(false);
|
||||
|
||||
@Mock PersistentEntity<?, P> entity;
|
||||
@Mock ParameterValueProvider<P> provider;
|
||||
@@ -83,7 +84,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
PreferredConstructor<Foo, P> constructor = PreferredConstructorDiscoverer.discover(Foo.class);
|
||||
|
||||
doReturn(Foo.class).when(entity).getType();
|
||||
doReturn(constructor).when(entity).getPersistenceConstructor();
|
||||
doReturn(constructor).when(entity).getEntityCreator();
|
||||
|
||||
assertThat(instance.createInstance(entity, provider)).isInstanceOf(Foo.class);
|
||||
|
||||
@@ -105,7 +106,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
void createsInnerClassInstanceCorrectly() {
|
||||
|
||||
var entity = new BasicPersistentEntity<Inner, P>(from(Inner.class));
|
||||
assertThat(entity.getPersistenceConstructor()).satisfies(constructor -> {
|
||||
assertThat(entity.getEntityCreator()).satisfies(constructor -> {
|
||||
|
||||
var parameter = constructor.getParameters().iterator().next();
|
||||
|
||||
@@ -189,12 +190,43 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
assertThat(reference.sample.name).isEqualTo("FOO");
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1175
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
void createsInstancesWithFactoryMethodCorrectly() {
|
||||
|
||||
PersistentEntity<WithFactoryMethod, P> entity = new BasicPersistentEntity<>(from(WithFactoryMethod.class));
|
||||
|
||||
doReturn(2L, "FOO").when(provider).getParameterValue(any(Parameter.class));
|
||||
|
||||
var provider = 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("name")) {
|
||||
return (T) "Walter";
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException(parameter.getName());
|
||||
}
|
||||
};
|
||||
|
||||
var result = this.instance.createInstance(entity, provider);
|
||||
|
||||
assertThat(result.id).isEqualTo(1L);
|
||||
assertThat(result.name).isEqualTo("Hello Walter");
|
||||
}
|
||||
|
||||
@Test // DATACMNS-578, DATACMNS-1126
|
||||
void instantiateObjCtorDefault() {
|
||||
|
||||
doReturn(ObjCtorDefault.class).when(entity).getType();
|
||||
doReturn(PreferredConstructorDiscoverer.discover(ObjCtorDefault.class))//
|
||||
.when(entity).getPersistenceConstructor();
|
||||
.when(entity).getEntityCreator();
|
||||
|
||||
IntStream.range(0, 2)
|
||||
.forEach(i -> assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(ObjCtorDefault.class));
|
||||
@@ -205,7 +237,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
|
||||
doReturn(ObjCtorNoArgs.class).when(entity).getType();
|
||||
doReturn(PreferredConstructorDiscoverer.discover(ObjCtorNoArgs.class))//
|
||||
.when(entity).getPersistenceConstructor();
|
||||
.when(entity).getEntityCreator();
|
||||
|
||||
IntStream.range(0, 2).forEach(i -> {
|
||||
|
||||
@@ -224,7 +256,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
|
||||
doReturn(ObjCtor1ParamString.class).when(entity).getType();
|
||||
doReturn(PreferredConstructorDiscoverer.discover(ObjCtor1ParamString.class))//
|
||||
.when(entity).getPersistenceConstructor();
|
||||
.when(entity).getEntityCreator();
|
||||
doReturn("FOO").when(provider).getParameterValue(any());
|
||||
|
||||
IntStream.range(0, 2).forEach(i -> {
|
||||
@@ -242,7 +274,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
|
||||
doReturn(ObjCtor2ParamStringString.class).when(entity).getType();
|
||||
doReturn(PreferredConstructorDiscoverer.discover(ObjCtor2ParamStringString.class))//
|
||||
.when(entity).getPersistenceConstructor();
|
||||
.when(entity).getEntityCreator();
|
||||
|
||||
IntStream.range(0, 2).forEach(i -> {
|
||||
|
||||
@@ -262,7 +294,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
|
||||
doReturn(ObjectCtor1ParamInt.class).when(entity).getType();
|
||||
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor1ParamInt.class))//
|
||||
.when(entity).getPersistenceConstructor();
|
||||
.when(entity).getEntityCreator();
|
||||
|
||||
IntStream.range(0, 2).forEach(i -> {
|
||||
|
||||
@@ -280,7 +312,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
|
||||
doReturn(ObjectCtor1ParamInt.class).when(entity).getType();
|
||||
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor1ParamInt.class))//
|
||||
.when(entity).getPersistenceConstructor();
|
||||
.when(entity).getEntityCreator();
|
||||
|
||||
assertThatThrownBy(() -> this.instance.createInstance(entity, provider)) //
|
||||
.hasCauseInstanceOf(IllegalArgumentException.class);
|
||||
@@ -292,7 +324,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
|
||||
doReturn(ObjectCtor7ParamsString5IntsString.class).when(entity).getType();
|
||||
doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor7ParamsString5IntsString.class))//
|
||||
.when(entity).getPersistenceConstructor();
|
||||
.when(entity).getEntityCreator();
|
||||
|
||||
IntStream.range(0, 2).forEach(i -> {
|
||||
|
||||
@@ -412,7 +444,7 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
|
||||
doReturn(type).when(entity).getType();
|
||||
doReturn(PreferredConstructorDiscoverer.discover(type))//
|
||||
.when(entity).getPersistenceConstructor();
|
||||
.when(entity).getEntityCreator();
|
||||
}
|
||||
|
||||
static class Foo {
|
||||
@@ -429,6 +461,23 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
|
||||
}
|
||||
}
|
||||
|
||||
static class WithFactoryMethod {
|
||||
|
||||
final Long id;
|
||||
final String name;
|
||||
|
||||
private WithFactoryMethod(Long id, String name) {
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@FactoryMethod
|
||||
public static WithFactoryMethod create(Long id, String name) {
|
||||
return new WithFactoryMethod(id, "Hello " + name);
|
||||
}
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
|
||||
final Long id;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2021 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.mapping.model;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.annotation.FactoryMethod;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link EntityCreatorMetadataDiscoverer}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class EntityCreatorMetadataDiscovererUnitTests {
|
||||
|
||||
@Test
|
||||
void shouldDiscoverAnnotatedFactoryMethod() {
|
||||
|
||||
var entity = new BasicPersistentEntity<>(ClassTypeInformation.from(FactoryMethodsPerson.class));
|
||||
var creator = EntityCreatorMetadataDiscoverer.discover(entity);
|
||||
|
||||
assertThat(creator).isInstanceOf(org.springframework.data.mapping.FactoryMethod.class);
|
||||
assertThat(((org.springframework.data.mapping.FactoryMethod<?, ?>) creator).getFactoryMethod().getParameterCount())
|
||||
.isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDiscoverAnnotatedConstructor() {
|
||||
|
||||
var entity = new BasicPersistentEntity<>(ClassTypeInformation.from(ConstructorPerson.class));
|
||||
var creator = EntityCreatorMetadataDiscoverer.discover(entity);
|
||||
|
||||
assertThat(creator).isInstanceOf(PreferredConstructor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDiscoverDefaultConstructor() {
|
||||
|
||||
var entity = new BasicPersistentEntity<>(ClassTypeInformation.from(Person.class));
|
||||
var creator = EntityCreatorMetadataDiscoverer.discover(entity);
|
||||
|
||||
assertThat(creator).isInstanceOf(PreferredConstructor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectNonStaticFactoryMethod() {
|
||||
assertThatExceptionOfType(MappingException.class)
|
||||
.isThrownBy(() -> new BasicPersistentEntity<>(ClassTypeInformation.from(NonStaticFactoryMethod.class)));
|
||||
}
|
||||
|
||||
static class Person {
|
||||
|
||||
private final String firstname, lastname;
|
||||
|
||||
private Person(String firstname, String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NonStaticFactoryMethod {
|
||||
|
||||
@FactoryMethod
|
||||
public ConstructorPerson of(String firstname, String lastname) {
|
||||
return new ConstructorPerson(firstname, lastname);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class FactoryMethodsPerson {
|
||||
|
||||
private final String firstname, lastname;
|
||||
|
||||
private FactoryMethodsPerson(String firstname, String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
|
||||
public static FactoryMethodsPerson of(String firstname) {
|
||||
return new FactoryMethodsPerson(firstname, "unknown");
|
||||
}
|
||||
|
||||
@FactoryMethod
|
||||
public static FactoryMethodsPerson of(String firstname, String lastname) {
|
||||
return new FactoryMethodsPerson(firstname, lastname);
|
||||
}
|
||||
}
|
||||
|
||||
static class ConstructorPerson {
|
||||
|
||||
private final String firstname, lastname;
|
||||
|
||||
private ConstructorPerson(String firstname, String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
|
||||
public static ConstructorPerson of(String firstname, String lastname) {
|
||||
return new ConstructorPerson(firstname, lastname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,13 +44,6 @@ class EntityInstantiatorsUnitTests {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new EntityInstantiators((EntityInstantiator) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void usesReflectionEntityInstantiatorAsDefaultFallback() {
|
||||
|
||||
var instantiators = new EntityInstantiators();
|
||||
assertThat(instantiators.getInstantiatorFor(entity)).isInstanceOf(ClassGeneratingEntityInstantiator.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsCustomInstantiatorForTypeIfRegistered() {
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2021 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.mapping.model;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.annotation.FactoryMethod;
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link org.springframework.data.mapping.FactoryMethod}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class FactoryMethodUnitTests {
|
||||
|
||||
private static EntityInstantiators instantiators = new EntityInstantiators();
|
||||
|
||||
@Test
|
||||
void shouldCreateInstanceThroughFactoryMethod() {
|
||||
|
||||
var entity = new BasicPersistentEntity<>(ClassTypeInformation.from(FactoryPerson.class));
|
||||
|
||||
var result = instantiators.getInstantiatorFor(entity).createInstance(entity,
|
||||
new ParameterValueProvider() {
|
||||
|
||||
@Override
|
||||
public Object getParameterValue(Parameter parameter) {
|
||||
|
||||
if (parameter.getName().equals("firstname")) {
|
||||
return "Walter";
|
||||
}
|
||||
|
||||
if (parameter.getName().equals("lastname")) {
|
||||
return "White";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(result.firstname).isEqualTo("Walter");
|
||||
assertThat(result.lastname).isEqualTo("Mr. White");
|
||||
}
|
||||
|
||||
static class FactoryPerson {
|
||||
|
||||
private final String firstname, lastname;
|
||||
|
||||
private FactoryPerson(String firstname, String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
|
||||
@FactoryMethod
|
||||
public static FactoryPerson of(String firstname, String lastname) {
|
||||
return new FactoryPerson(firstname, "Mr. " + lastname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,8 @@ import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
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.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -55,7 +55,7 @@ class PersistentEntityParameterValueProviderUnitTests<P extends PersistentProper
|
||||
}
|
||||
};
|
||||
|
||||
assertThat(entity.getPersistenceConstructor()).satisfies(constructor -> {
|
||||
assertThat(entity.getEntityCreator()).satisfies(constructor -> {
|
||||
|
||||
var iterator = constructor.getParameters().iterator();
|
||||
ParameterValueProvider<P> provider = new PersistentEntityParameterValueProvider<>(entity, propertyValueProvider,
|
||||
@@ -74,7 +74,7 @@ class PersistentEntityParameterValueProviderUnitTests<P extends PersistentProper
|
||||
ParameterValueProvider<P> provider = new PersistentEntityParameterValueProvider<>(entity, propertyValueProvider,
|
||||
Optional.of(property));
|
||||
|
||||
assertThat(entity.getPersistenceConstructor())
|
||||
assertThat(entity.getEntityCreator())
|
||||
.satisfies(constructor -> assertThatExceptionOfType(MappingException.class)//
|
||||
.isThrownBy(() -> provider.getParameterValue(constructor.getParameters().iterator().next()))//
|
||||
.withMessageContaining("bar")//
|
||||
|
||||
@@ -30,10 +30,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
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.ReflectionEntityInstantiatorUnitTests.Outer.Inner;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -69,7 +69,7 @@ class ReflectionEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
|
||||
|
||||
PreferredConstructor<Foo, P> constructor = PreferredConstructorDiscoverer.discover(Foo.class);
|
||||
|
||||
doReturn(constructor).when(entity).getPersistenceConstructor();
|
||||
doReturn(constructor).when(entity).getEntityCreator();
|
||||
|
||||
var instance = INSTANCE.createInstance(entity, provider);
|
||||
|
||||
@@ -92,7 +92,7 @@ class ReflectionEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
|
||||
void createsInnerClassInstanceCorrectly() {
|
||||
|
||||
var entity = new BasicPersistentEntity<Inner, P>(from(Inner.class));
|
||||
assertThat(entity.getPersistenceConstructor()).satisfies(it -> {
|
||||
assertThat(entity.getEntityCreator()).satisfies(it -> {
|
||||
|
||||
var parameter = it.getParameters().iterator().next();
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
import org.springframework.data.mapping.model.AbstractPersistentPropertyUnitTests.SamplePersistentProperty;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user