/*
* Copyright 2014-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.convert;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.util.ClassTypeInformation.*;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
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
*/
@RunWith(MockitoJUnitRunner.class)
public class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
ClassGeneratingEntityInstantiator instance = new ClassGeneratingEntityInstantiator();
@Mock PersistentEntity<?, P> entity;
@Mock ParameterValueProvider<P> provider;
@Mock PreferredConstructor<?, P> constructor;
@Mock Parameter<?, P> parameter;
@Before
public void setUp() {
doReturn(Optional.empty()).when(entity).getPersistenceConstructor();
}
@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
public void instantiatesTypeWithPreferredConstructorUsingParameterValueProvider() {
Optional<? extends PreferredConstructor<Foo, P>> constructor = new PreferredConstructorDiscoverer<Foo, P>(Foo.class)
.getConstructor();
doReturn(Foo.class).when(entity).getType();
doReturn(constructor).when(entity).getPersistenceConstructor();
assertThat(instance.createInstance(entity, provider)).isInstanceOf(Foo.class);
assertThat(constructor).hasValueSatisfying(it -> verify(provider, times(1)).getParameterValue(it.getParameters().iterator().next()));
}
@Test(expected = MappingInstantiationException.class) // DATACMNS-300, DATACMNS-578
@SuppressWarnings({ "unchecked", "rawtypes" })
public void throwsExceptionOnBeanInstantiationException() {
doReturn(Optional.empty()).when(entity).getPersistenceConstructor();
doReturn(PersistentEntity.class).when(entity).getType();
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()).hasValueSatisfying(constructor -> {
Parameter<Object, P> parameter = constructor.getParameters().iterator().next();
Object outer = new Outer();
doReturn(Optional.of(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(Optional.of("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-578
@SuppressWarnings({ "unchecked", "rawtypes" })
public void instantiateObjCtorDefault() {
doReturn(ObjCtorDefault.class).when(entity).getType();
doReturn(new PreferredConstructorDiscoverer<>(ObjCtorDefault.class).getConstructor())//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2).forEach(i -> assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(ObjCtorDefault.class));
}
@Test // DATACMNS-578
@SuppressWarnings({ "unchecked", "rawtypes" })
public void instantiateObjCtorNoArgs() {
doReturn(ObjCtorNoArgs.class).when(entity).getType();
doReturn(new PreferredConstructorDiscoverer<>(ObjCtorNoArgs.class).getConstructor())//
.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
@SuppressWarnings("unchecked")
public void instantiateObjCtor1ParamString() {
doReturn(ObjCtor1ParamString.class).when(entity).getType();
doReturn(new PreferredConstructorDiscoverer<>(ObjCtor1ParamString.class).getConstructor())//
.when(entity).getPersistenceConstructor();
doReturn(Optional.of("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
@SuppressWarnings("unchecked")
public void instantiateObjCtor2ParamStringString() {
doReturn(ObjCtor2ParamStringString.class).when(entity).getType();
doReturn(new PreferredConstructorDiscoverer<>(ObjCtor2ParamStringString.class).getConstructor())//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2).forEach(i -> {
when(provider.getParameterValue(any())).thenReturn(Optional.of("FOO"), Optional.of("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
@SuppressWarnings("unchecked")
public void instantiateObjectCtor1ParamInt() {
doReturn(ObjectCtor1ParamInt.class).when(entity).getType();
doReturn(new PreferredConstructorDiscoverer<>(ObjectCtor1ParamInt.class).getConstructor())//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2).forEach(i -> {
doReturn(Optional.of(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-578
@SuppressWarnings("unchecked")
public void instantiateObjectCtor7ParamsString5IntsString() {
doReturn(ObjectCtor7ParamsString5IntsString.class).when(entity).getType();
doReturn(new PreferredConstructorDiscoverer<>(ObjectCtor7ParamsString5IntsString.class).getConstructor())//
.when(entity).getPersistenceConstructor();
IntStream.range(0, 2).forEach(i -> {
when(provider.getParameterValue(any(Parameter.class))).thenReturn(Optional.of("A"), Optional.of(1),
Optional.of(2), Optional.of(3), Optional.of(4), Optional.of(5), Optional.of("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");
});
}
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;
}
}
/**
* @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;
}
}
}