DATACMNS-1200 - Polishing.

Throw MappingInstantiationException from KotlinClassGeneratingEntityInstantiator if instantiation fails to align behavior with ClassGeneratingEntityInstantiator. Report Kotlin constructor instead of Java constructor if available.

Original pull request: #255.
This commit is contained in:
Mark Paluch
2017-10-18 14:48:56 +02:00
committed by Oliver Gierke
parent f45e2be383
commit c6d880309d
3 changed files with 54 additions and 3 deletions

View File

@@ -20,6 +20,7 @@ import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
@@ -27,6 +28,7 @@ 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.MappingInstantiationException;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.lang.Nullable;
@@ -197,7 +199,17 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
PreferredConstructor<? extends T, P> preferredConstructor = entity.getPersistenceConstructor();
Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider);
try {
return (T) instantiator.newInstance(params);
} catch (Exception e) {
throw new MappingInstantiationException(entity, Arrays.asList(params), e);
}
}
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
@Nullable PreferredConstructor<? extends T, P> preferredConstructor, ParameterValueProvider<P> provider) {
if (preferredConstructor == null) {
throw new IllegalArgumentException("PreferredConstructor must not be null!");
@@ -237,7 +249,7 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti
params[userParameterCount + i] = defaulting[i];
}
return (T) instantiator.newInstance(params);
return params;
}
}
}

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.data.mapping.model;
import kotlin.reflect.KFunction;
import kotlin.reflect.jvm.ReflectJvmMapping;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
@@ -22,6 +25,7 @@ import java.util.Optional;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
@@ -88,12 +92,28 @@ public class MappingInstantiationException extends RuntimeException {
}
return String.format(TEXT_TEMPLATE, it.getType().getName(),
constructor.map(c -> c.getConstructor().toString()).orElse("NO_CONSTRUCTOR"), //
constructor.map(c -> toString(c)).orElse("NO_CONSTRUCTOR"), //
String.join(",", toStringArgs));
}).orElse(defaultMessage);
}
private static String toString(PreferredConstructor<?, ?> preferredConstructor) {
Constructor<?> constructor = preferredConstructor.getConstructor();
if (ReflectionUtils.isSupportedKotlinClass(constructor.getDeclaringClass())) {
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(constructor);
if (kotlinFunction != null) {
return kotlinFunction.toString();
}
}
return constructor.toString();
}
/**
* Returns the type of the entity that was attempted to instantiate.
*

View File

@@ -25,8 +25,10 @@ import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.springframework.data.mapping.PersistentEntity
import org.springframework.data.mapping.context.SamplePersistentProperty
import org.springframework.data.mapping.model.MappingInstantiationException
import org.springframework.data.mapping.model.ParameterValueProvider
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer
import java.lang.IllegalArgumentException
/**
* Unit tests for [KotlinClassGeneratingEntityInstantiator] creating instances using Kotlin data classes.
@@ -79,6 +81,21 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
Assertions.assertThat(instance.prop34).isEqualTo("White")
}
@Test // DATACMNS-1200
fun `absent primitive value should cause MappingInstantiationException`() {
val entity = this.entity as PersistentEntity<WithBoolean, SamplePersistentProperty>
val constructor = PreferredConstructorDiscoverer.discover<WithBoolean, SamplePersistentProperty>(WithBoolean::class.java)
doReturn(constructor).whenever(entity).persistenceConstructor
doReturn(constructor.constructor.declaringClass).whenever(entity).type
Assertions.assertThatThrownBy { KotlinClassGeneratingEntityInstantiator().createInstance(entity, provider) } //
.isInstanceOf(MappingInstantiationException::class.java) //
.hasMessageContaining("fun <init>(kotlin.Boolean)") //
.hasCauseInstanceOf(IllegalArgumentException::class.java)
}
data class Contact(val firstname: String, val lastname: String)
data class ContactWithDefaulting(val prop0: String, val prop1: String = "White", val prop2: String,
@@ -94,5 +111,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
val prop30: String = "White", val prop31: String = "White", val prop32: String = "White",
val prop33: String, val prop34: String = "White"
)
data class WithBoolean(val state: Boolean)
}