Closed spring-projects-issues closed 6 years ago
Oliver Drotbohm commented
Looks like we need to be more defensive about the copy(…)
method we select and make sure it actually takes all parameters we need to hand it over, right?
Marian Jureczko commented
I am not sure what exactly was going on there. When decompiling CatalogueTags_Accessor_jcjqjz.setProperty(), there was nothing wrong, i.e. the correct copy method was called. But, with a black box approach, when there is an additional copy method with different arguments (DateTimeRange in my case), the additional one is called in place of the data class default one (accepting all class fields) when trying to set version of a new entity to 0. At least that is the only explanation I could come up with to justify the unsuccessful Int to DateTimeRange casting
Mark Paluch commented
The culprit here is an optimization that we made for single-property classes (value types). We attempt to find a public
copy method that takes a single argument and returns the entity type so it does not require setup of defaulting masks.
We should guard the optimization with a check on the parameter type and make sure the entity consists of a single property to use the public
copy optimization
Mark Paluch commented
I moved this ticket to Spring Data Commons as the fix will happen here
Mark Paluch commented
I pushed a change to a bugfix branch and we have a snapshot build available
It would be cool if you could give the snapshots a try (use version 2.1.0.DATACMNS-1391-SNAPSHOT
) and see if this behaves as expected.
Marcin Sokołowski commented
Another problem occurred, I've prepared some minimal working example here: https://github.com/sokolek/DATACMNS-1391
Method
org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory#findPublicCopyMethod:1466
sometimes picks copy
method from the data class , and sometimes overloaded copy
method, because type.getDeclaredMethods())
returns them in random order. Result of findPublicCopyMethod
method is then indirectly assigned in ClassGeneratingPropertyAccessorFactory.visitKotlinCopy:1102
to KotlinDefaultCopyMethod defaultMethod
, but unfortunately this could be overloaded version of copy, which is not copy$default
.
!screenshot-dataclass.png|width=1525,height=1017!
In result following Exception is thrown:
java.lang.RuntimeException: java.lang.IllegalArgumentException: Cannot resolve parameter name id to a index in method copy!
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.createAccessorClass(ClassGeneratingPropertyAccessorFactory.java:206)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.potentiallyCreateAndRegisterPersistentPropertyAccessorClass(ClassGeneratingPropertyAccessorFactory.java:190)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.getPropertyAccessor(ClassGeneratingPropertyAccessorFactory.java:100)
at org.springframework.data.mapping.model.BasicPersistentEntity.getPropertyAccessor(BasicPersistentEntity.java:455)
at org.springframework.data.mapping.model.PersistentEntityIsNewStrategy.lambda$new$0(PersistentEntityIsNewStrategy.java:48)
at org.springframework.data.mapping.model.PersistentEntityIsNewStrategy.isNew(PersistentEntityIsNewStrategy.java:95)
at org.springframework.data.mapping.model.BasicPersistentEntity.isNew(BasicPersistentEntity.java:483)
at org.springframework.data.repository.core.support.PersistentEntityInformation.isNew(PersistentEntityInformation.java:44)
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:80)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy94.save(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:338)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:197)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy94.save(Unknown Source)
at com.mytest.MyTest.send new dtc product(MyTest.kt:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.IllegalArgumentException: Cannot resolve parameter name id to a index in method copy!
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$KotlinCopyByProperty.findIndex(ClassGeneratingPropertyAccessorFactory.java:1562)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$KotlinCopyByProperty.<init>(ClassGeneratingPropertyAccessorFactory.java:1549)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$KotlinDefaultCopyMethod.forProperty(ClassGeneratingPropertyAccessorFactory.java:1531)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$PropertyAccessorClassGenerator.visitKotlinCopy(ClassGeneratingPropertyAccessorFactory.java:1103)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$PropertyAccessorClassGenerator.visitSetProperty0(ClassGeneratingPropertyAccessorFactory.java:1014)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$PropertyAccessorClassGenerator.visitSetPropertySwitch(ClassGeneratingPropertyAccessorFactory.java:986)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$PropertyAccessorClassGenerator.visitSetProperty(ClassGeneratingPropertyAccessorFactory.java:936)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$PropertyAccessorClassGenerator.generateBytecode(ClassGeneratingPropertyAccessorFactory.java:350)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$PropertyAccessorClassGenerator.generateCustomAccessorClass(ClassGeneratingPropertyAccessorFactory.java:326)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.createAccessorClass(ClassGeneratingPropertyAccessorFactory.java:204)
... 73 more
Mark Paluch commented
Thanks a lot. I'll have a look
Mark Paluch commented
I pushed another change to fix the issue. Care to give it a spin? (2.1.0.DATACMNS-1391-SNAPSHOT
resolving to 2.1.0.DATACMNS-1391-20180914.084050-5
)
Marcin Sokołowski commented
Thanks for quick response. It works for the current setup, but it won't if the name of the arguments in overloaded copy method will be the same as the actual property names. See current master on https://github.com/sokolek/DATACMNS-1391/
The problem is caused by the logic in filtering at org.springframework.data.mapping.model.KotlinCopyMethod#findPublicCopyMethod:149-176
. I think that the method should not rely how the parameters are named, but rather on their order, types and the total number of parameters. I guess that the intention there is just to find copy
method generated by Kotlin (not the static copy$default
one, and not the any overloaded copy
from upper class), is my understanding correct?
Let me ask just one more question, why not just always use static copy$default
method? I think you don't want to rely on the implementation details of copy methods by kotlin (as fas as I know, copy$default
isn't documented anywhere), is that true?
Mark Paluch commented
We have different approaches and cases where we need to distinguish between the public and the synthetic methods. Using reflection, we're using Kotlin's reflection API to invoke methods and to let Kotlin reflection apply defaulting. To do so, we need to lookup a KCallable
. This is only possible by using the public generated copy
method. Invoking ReflectJvmMapping.getKotlinFunction(…)
with the copy$default
method isn't able to resolve to a KCallable
but returns null
. Kotlin reflection is only able to resolve regular methods to KCallable
and cannot use the synthetic ones.
So we need to find the generated copy
method. There's next to no documentation on how to achieve that so we're still investigating. It looks as if the copy method has the same signature as the primary constructor and we should add another validation level.
Mark Paluch commented
We aligned the copy method discovery to use the primary constructor signature. A new build is available
Marcin Sokołowski commented
Now it works like a charm
Mark Paluch commented
Thanks a lot for verifying the patch. Creating a pull request so we can include the change in our upcoming release
Marian Jureczko opened DATACMNS-1391 and commented
Given (in kotlin):
When:
Then:
Affects: 2.1 RC2 (Lovelace)
Attachments:
Referenced from: pull request https://github.com/spring-projects/spring-data-commons/pull/312
1 votes, 4 watchers