From 506fe37aea97a497b955cd38d8b40240560cabd5 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 18 Oct 2017 14:48:56 +0200 Subject: [PATCH] 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. --- ...tlinClassGeneratingEntityInstantiator.java | 16 ++++++++++++-- .../model/MappingInstantiationException.java | 22 ++++++++++++++++++- ...ssGeneratingEntityInstantiatorUnitTests.kt | 19 ++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java index 1bdb23a3a..05b7d5cd3 100644 --- a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java @@ -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 , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { - PreferredConstructor 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

, T> Object[] extractInvocationArguments( + @Nullable PreferredConstructor preferredConstructor, ParameterValueProvider

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; } } } diff --git a/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java b/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java index 68e7a8299..37bbe9961 100644 --- a/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java +++ b/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java @@ -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. * diff --git a/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt b/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt index 0c3f820c0..a2d05cb66 100644 --- a/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt @@ -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 + val constructor = PreferredConstructorDiscoverer.discover(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 (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) }