/*
* Copyright 2012-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.convert.ReflectionEntityInstantiator.*;
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 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.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
*/
@RunWith(MockitoJUnitRunner.class)
public class ReflectionEntityInstantiatorUnitTests<P extends PersistentProperty<P>> {
@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();
INSTANCE.createInstance(entity, provider);
}
@Test
public void instantiatesArrayCorrectly() {
doReturn(String[][].class).when(entity).getType();
INSTANCE.createInstance(entity, provider);
}
@Test
public void instantiatesTypeWithPreferredConstructorUsingParameterValueProvider() {
Optional<? extends PreferredConstructor<Foo, P>> constructor = new PreferredConstructorDiscoverer<Foo, P>(Foo.class)
.getConstructor();
doReturn(constructor).when(entity).getPersistenceConstructor();
doReturn(Optional.empty()).when(provider).getParameterValue(any());
Object instance = INSTANCE.createInstance(entity, provider);
assertThat(instance).isInstanceOf(Foo.class);
assertThat(constructor).hasValueSatisfying(it -> verify(provider, times(1)).getParameterValue(it.getParameters().iterator().next()));
}
@Test(expected = MappingInstantiationException.class) // DATACMNS-300
@SuppressWarnings({ "unchecked", "rawtypes" })
public void throwsExceptionOnBeanInstantiationException() {
doReturn(Optional.empty()).when(entity).getPersistenceConstructor();
doReturn(PersistentEntity.class).when(entity).getType();
INSTANCE.createInstance(entity, provider);
}
@Test // DATACMNS-134
public void createsInnerClassInstanceCorrectly() {
BasicPersistentEntity<Inner, P> entity = new BasicPersistentEntity<>(from(Inner.class));
assertThat(entity.getPersistenceConstructor()).hasValueSatisfying(it -> {
Parameter<Object, P> parameter = it.getParameters().iterator().next();
Object outer = new Outer();
when(provider.getParameterValue(parameter)).thenReturn(Optional.of(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(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 {
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;
}
}
}