spring-projects / spring-data-couchbase

Provides support to increase developer productivity in Java when using Couchbase. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
https://spring.io/projects/spring-data-couchbase
Apache License 2.0
274 stars 190 forks source link

"TypeAlias" does't work along with abstraction #1914

Closed muhdalavu closed 4 months ago

muhdalavu commented 6 months ago

While I am trying to use the TypeAlieas along with an implemented interface the resolver is not able to resolve the implemented class.

Details step.

Class structure

    interface Node{
        String name();
        Collection<Node> getChildren();
    }

    @TypeAlias("Root")
    @Document
    class Root implement Node{
        private String name;
        private String id;
        private Collection<Node> nodes;

        Collection<Node> getChildren(){
            return nodes;
        }
    }

    @TypeAlias("B")
    class A implement Node{
        private String name;
        private Collection<Node> nodes;

        Collection<Node> getChildren(){
            return nodes;
        }
    }

    @TypeAlias("B")
    class B implement Node{
        private String name;
        private Collection<Node> nodes;

        Collection<Node> getChildren(){
            return nodes;
        }

    }

then we have the repository

@Repository
public interface InductionRepository extends CrudRepository<Root, String> {

}

here while trying to save the data using the repository it works as expected. the _class field in the document comes as expected depends on the object type. But the problem comes while retrieving the data. The below error comes while getting the data using the repository.

Failed to instantiate [Node]: Specified class is an interface

Please help

mikereiche commented 6 months ago

Can you please show

What happens when you don't use @TypeAlias?

muhdalavu commented 6 months ago

Hello @mikereiche,

Please find the details below. version: spring-data-couchbase: 5.2.3

Working case

public interface InductNodeModel {
    String name();
    Collection<InductNodeModel> childList();
}

@Document
//@TypeAlias("InductionModel")
public record InductionModel (@Id String inductionId,
                              String name,
                              Collection<InductNodeModel> childList) implements InductNodeModel {
}

//@TypeAlias("NodeTypeB")
public record NodeTypeA(String name, List<InductNodeModel> children) implements InductNodeModel {
    @Override
    public Collection<InductNodeModel> childList() {
        return children;
    }
}

//@TypeAlias("NodeTypeB")
public record NodeTypeB(String name, List<InductNodeModel> children) implements InductNodeModel {
    @Override
    public Collection<InductNodeModel> childList() {
        return children;
    }
}

@Repository
public interface InductionRepository extends CrudRepository<InductionModel, String> {

}

NodeTypeA child1 = new NodeTypeA("Child1", new ArrayList<>());
NodeTypeA child2 = new NodeTypeA("Child2", new ArrayList<>());
NodeTypeA child3 = new NodeTypeA("Child3", new ArrayList<>());
NodeTypeA nodeTypeA = new NodeTypeA("nodeTypeA", List.of(child1, child2));
NodeTypeB nodeTypeB = new NodeTypeB("nodeTypeB", List.of(child3));

InductionModel inductionModel = new InductionModel(id, "test", List.of(nodeTypeA, nodeTypeB));

inductionRepository.save(inductionModel);
Optional<InductionModel> byId = inductionRepository.findById(id);

Above code works without any issue, please note here I have commented TypeAlias annotation. below is the document saved in the DB.

{
  "_class": "com.test.domain.model.Root",
  "childList": [
    {
      "_class": "com.test.domain.model.test.NodeTypeA",
      "children": [
        {
          "_class": "com.test.domain.model.test.NodeTypeA",
          "children": [],
          "name": "Child1"
        },
        {
          "_class": "com.test.domain.model.test.NodeTypeA",
          "children": [],
          "name": "Child2"
        }
      ],
      "name": "nodeTypeA"
    },
    {
      "_class": "com.test.domain.model.test.NodeTypeB",
      "children": [
        {
          "_class": "com.test.domain.model.test.NodeTypeA",
          "children": [],
          "name": "Child3"
        }
      ],
      "name": "nodeTypeB"
    }
  ],
  "name": "test"
}

Not working case

As soon as I uncomment the TypeAlias, the document will persist but it fails to retrieve back again while querying. below is the document persisted.

{
  "_class": "InductionModel",
  "childList": [
    {
      "_class": "NodeTypeB",
      "children": [
        {
          "_class": "NodeTypeB",
          "children": [],
          "name": "Child1"
        },
        {
          "_class": "NodeTypeB",
          "children": [],
          "name": "Child2"
        }
      ],
      "name": "nodeTypeA"
    },
    {
      "_class": "NodeTypeB",
      "children": [
        {
          "_class": "NodeTypeB",
          "children": [],
          "name": "Child3"
        }
      ],
      "name": "nodeTypeB"
    }
  ],
  "name": "test"
}

Stack trace

org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.test.induct.domain.model.InductNodeModel using constructor NO_CONSTRUCTOR with arguments 
    at org.springframework.data.mapping.model.ReflectionEntityInstantiator.instantiateClass(ReflectionEntityInstantiator.java:142)
    Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 
Assembly trace from producer [reactor.core.publisher.MonoFlatMap] :
    reactor.core.publisher.Mono.flatMap(Mono.java:3116)
    org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport$ReactiveFindByIdSupport.lambda$one$4(ReactiveFindByIdOperationSupport.java:118)
Error has been observed at the following site(s):
    *________Mono.flatMap ⇢ at org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport$ReactiveFindByIdSupport.lambda$one$4(ReactiveFindByIdOperationSupport.java:118)
    *________Mono.flatMap ⇢ at org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport$ReactiveFindByIdSupport.one(ReactiveFindByIdOperationSupport.java:106)
    |_ Mono.onErrorResume ⇢ at org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport$ReactiveFindByIdSupport.one(ReactiveFindByIdOperationSupport.java:129)
    |_    Mono.onErrorMap ⇢ at org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport$ReactiveFindByIdSupport.one(ReactiveFindByIdOperationSupport.java:134)
    *__________Mono.error ⇢ at org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport$ReactiveFindByIdSupport.lambda$one$5(ReactiveFindByIdOperationSupport.java:133)
Original Stack Trace:
        at org.springframework.data.mapping.model.ReflectionEntityInstantiator.instantiateClass(ReflectionEntityInstantiator.java:142)
        at org.springframework.data.mapping.model.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:57)
        at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:98)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:270)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:252)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.readCollection(MappingCouchbaseConverter.java:814)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.readValue(MappingCouchbaseConverter.java:978)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter$CouchbasePropertyValueProvider.getPropertyValue(MappingCouchbaseConverter.java:1118)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter$CouchbasePropertyValueProvider.getPropertyValue(MappingCouchbaseConverter.java:1052)
        at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:71)
        at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:49)
        at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:301)
        at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:273)
        at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:98)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:270)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:252)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:209)
        at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:94)
        at org.springframework.data.couchbase.core.AbstractTemplateSupport.decodeEntityBase(AbstractTemplateSupport.java:122)
        at org.springframework.data.couchbase.core.CouchbaseTemplateSupport.decodeEntity(CouchbaseTemplateSupport.java:67)
        at org.springframework.data.couchbase.core.NonReactiveSupportWrapper.lambda$decodeEntity$1(NonReactiveSupportWrapper.java:47)
        at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:67)
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:146)
        at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
        at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113)
        at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1865)
        at com.couchbase.client.core.Reactor$SilentMonoCompletionStage.lambda$subscribe$0(Reactor.java:213)
        at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:863)
        at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:841)
        at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
        at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2179)
        at com.couchbase.client.core.msg.BaseRequest.succeed(BaseRequest.java:161)
        at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decodeAndComplete(KeyValueMessageHandler.java:441)
        at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.retryOrComplete(KeyValueMessageHandler.java:394)
        at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decode(KeyValueMessageHandler.java:355)
        at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.channelRead(KeyValueMessageHandler.java:276)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at com.couchbase.client.core.io.netty.kv.MemcacheProtocolVerificationHandler.channelRead(MemcacheProtocolVerificationHandler.java:85)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
        at com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at com.couchbase.client.core.deps.io.netty.handler.flush.FlushConsolidationHandler.channelRead(FlushConsolidationHandler.java:152)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
        at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at com.couchbase.client.core.deps.io.netty.channel.kqueue.AbstractKQueueStreamChannel$KQueueStreamUnsafe.readReady(AbstractKQueueStreamChannel.java:544)
        at com.couchbase.client.core.deps.io.netty.channel.kqueue.AbstractKQueueChannel$AbstractKQueueUnsafe.readReady(AbstractKQueueChannel.java:387)
        at com.couchbase.client.core.deps.io.netty.channel.kqueue.KQueueEventLoop.processReady(KQueueEventLoop.java:218)
        at com.couchbase.client.core.deps.io.netty.channel.kqueue.KQueueEventLoop.run(KQueueEventLoop.java:296)
        at com.couchbase.client.core.deps.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
        at com.couchbase.client.core.deps.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at com.couchbase.client.core.deps.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:1583)
    Suppressed: java.lang.Exception: #block terminated with an error
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:103)
        at reactor.core.publisher.Mono.block(Mono.java:1728)
        at org.springframework.data.couchbase.core.ExecutableFindByIdOperationSupport$ExecutableFindByIdSupport.one(ExecutableFindByIdOperationSupport.java:71)
        at org.springframework.data.couchbase.repository.support.SimpleCouchbaseRepository.findById(SimpleCouchbaseRepository.java:92)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
        at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277)
        at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
        at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
        at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)
        at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
        at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at org.springframework.data.couchbase.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:173)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:220)
        at jdk.proxy2/jdk.proxy2.$Proxy167.findById(Unknown Source)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:220)
        at jdk.proxy2/jdk.proxy2.$Proxy167.findById(Unknown Source)
        at com.test.induct.domain.application.command.InductionCommand.crateNewProductInduction(InductionCommand.java:37)
        at com.test.induct.core.Application.lambda$get$0(Application.java:34)
        at org.springframework.boot.SpringApplication.lambda$callRunner$5(SpringApplication.java:790)
        at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:83)
        at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60)
        at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:88)
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798)
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:789)
        at org.springframework.boot.SpringApplication.lambda$callRunners$3(SpringApplication.java:774)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
        at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:341)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343)
        at com.test.induct.core.Application.main(Application.java:28)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.test.induct.domain.model.InductNodeModel]: Specified class is an interface
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:137)
    at org.springframework.data.mapping.model.ReflectionEntityInstantiator.instantiateClass(ReflectionEntityInstantiator.java:139)
    at org.springframework.data.mapping.model.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:57)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:98)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:270)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:252)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.readCollection(MappingCouchbaseConverter.java:814)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.readValue(MappingCouchbaseConverter.java:978)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter$CouchbasePropertyValueProvider.getPropertyValue(MappingCouchbaseConverter.java:1118)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter$CouchbasePropertyValueProvider.getPropertyValue(MappingCouchbaseConverter.java:1052)
    at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:71)
    at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:49)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:301)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:273)
    at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:98)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:270)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:252)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:209)
    at org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:94)
    at org.springframework.data.couchbase.core.AbstractTemplateSupport.decodeEntityBase(AbstractTemplateSupport.java:122)
    at org.springframework.data.couchbase.core.CouchbaseTemplateSupport.decodeEntity(CouchbaseTemplateSupport.java:67)
    at org.springframework.data.couchbase.core.NonReactiveSupportWrapper.lambda$decodeEntity$1(NonReactiveSupportWrapper.java:47)
    at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:67)
    at reactor.core.publisher.MonoCallableOnAssembly.call(MonoCallableOnAssembly.java:91)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:146)
    at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
    at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
    at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
    at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
    at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
    at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113)
    at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1865)
    at com.couchbase.client.core.Reactor$SilentMonoCompletionStage.lambda$subscribe$0(Reactor.java:213)
    at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:863)
    at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:841)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2179)
    at com.couchbase.client.core.msg.BaseRequest.succeed(BaseRequest.java:161)
    at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decodeAndComplete(KeyValueMessageHandler.java:441)
    at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.retryOrComplete(KeyValueMessageHandler.java:394)
    at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decode(KeyValueMessageHandler.java:355)
    at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.channelRead(KeyValueMessageHandler.java:276)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at com.couchbase.client.core.io.netty.kv.MemcacheProtocolVerificationHandler.channelRead(MemcacheProtocolVerificationHandler.java:85)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
    at com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at com.couchbase.client.core.deps.io.netty.handler.flush.FlushConsolidationHandler.channelRead(FlushConsolidationHandler.java:152)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
    at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at com.couchbase.client.core.deps.io.netty.channel.kqueue.AbstractKQueueStreamChannel$KQueueStreamUnsafe.readReady(AbstractKQueueStreamChannel.java:544)
    at com.couchbase.client.core.deps.io.netty.channel.kqueue.AbstractKQueueChannel$AbstractKQueueUnsafe.readReady(AbstractKQueueChannel.java:387)
    at com.couchbase.client.core.deps.io.netty.channel.kqueue.KQueueEventLoop.processReady(KQueueEventLoop.java:218)
    at com.couchbase.client.core.deps.io.netty.channel.kqueue.KQueueEventLoop.run(KQueueEventLoop.java:296)
    at com.couchbase.client.core.deps.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
    at com.couchbase.client.core.deps.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at com.couchbase.client.core.deps.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:1583)

my investigation.

The framework tries to create an instance of an interface based on the POJO configured in the repository. The TypeAlias is not being used to get additional meta about the POJO.

Let me know if you need any further details.

Thank you.

mikereiche commented 5 months ago

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.test.induct.domain.model.InductNodeModel]: Specified class is an interface

Before you overrode the value of the _class property, it was the actual fully-qualified class name - _class=com.test.domain.model.test.NodeTypeA which is a class, and spring data could figure out how to create that object. But now you've overriden that _class=NodeTypeB - which is not a fully-qualified classname, so all spring-data has to go on is they type it is declared as - which is InductNodeModel - which is an interface.

public record InductionModel (@Id String inductionId,
                              String name,
                              Collection<InductNodeModel> childList) implements InductNodeModel {
}

Unless you want to write a decoder that figures out if a document containing _class:NodeTypeB is a com.test.domain.model.test.NodeTypeA or a com.test.domain.model.test.NodeTypeB, then you are better off not overriding @TypeAlias.

muhdalavu commented 5 months ago

Thanks for the response @mikereiche.

Can't this be considered an issue in the library? I hope the required metadata about each type is available in the resolution context. Let me know if I need to check for a fix for the issue.

mikereiche commented 5 months ago

If you explain what the use-case is, I can likely come up with a solution.

I hope the required metadata about each type is available in the resolution context

The metadata was available, until you used the @TypeAlias to override it. Without @TypeAlias - where would that information come from? You could define a repository interface parameterized on com.test.domain.model.test.NodeTypeA and one parameterized on com.test.domain.model.test.NodeTypeB, but then you would need to know what you were retrieving to use the correct repository. You could also use the template API which takes a Class argument - but again, you'd need to know what you were retrieving.

In this (spring-data-couchbase) repository under src/test/.../domain, there is an AbstractingTypeMapper that would allow you to customize the type to be used.

muhdalavu commented 5 months ago

Sorry @mikereiche , I am not able to understand the point below.

The metadata was available, until you used the typealias to override it. Without typealias - where would that information come from?

I have already annotated the Pojo with the typealias.

Here is my use case. I have a Pojo with some abstract collection.

interface Node{
        String name();
        Collection<Node> getChildren();
    }

    @TypeAlias("Root")
    @Document
    class Root implement Node{
        private String name;
        private String id;
        private Collection<Node> nodes;

        Collection<Node> getChildren(){
            return nodes;
        }
    }

    @TypeAlias("B")
    class A implement Node{
        private String name;
        private Collection<Node> nodes;

        Collection<Node> getChildren(){
            return nodes;
        }
    }

    @TypeAlias("B")
    class B implement Node{
        private String name;
        private Collection<Node> nodes;

        Collection<Node> getChildren(){
            return nodes;
        }

    }

Using the below repository I want to load the POJO. But it fails if I use TypeAlias, without it it works as expected. Here is the repository being used.

@Repository
public interface InductionRepository extends CrudRepository<Root, String> {

}
mikereiche commented 5 months ago

The metadata was available,

Yes, it's right here "_class": "com.test.domain.model.Root"

But you've overridden it, and now it is "_class": "InductionModel", which is not the fully-qualifed name of a type.

I have already annotated the Pojo with the typealias.

Yes. And as both you and I have noted, this is breaking your use case. You have overwritten the correct, default value used in the _class property that is stored in the Couchbase document with a value which is not useful - it is not the fully-qualified name of the class that was stored. So do not do that. (I asked why you are doing that "If you explain what the use-case is, I can likely come up with a solution" and you did not provide a reason, so I assume there is no reason).

@TypeAlias("Root")  // DO NOT DO THIS
@TypeAlias("InductionModel") // DO NOT DO THIS
@TypeAlias("B") // DO NOT DO THIS

  "_class": "com.test.domain.model.test.NodeTypeA", // this is a fully-qualified class name. It will work.
  "_class": "NodeTypeB", // this is NOT a fully-qualified classname, it will not work.

"NodeTypeB" (or "Root" or "InductionModel" or "B") is not a fully-qualified class name. spring data tries to find a class for that name, but none exists. So the only other information it has is the type name used in the definition of the repository. And what you have provided is an Interface. Interfaces do not have constructors, so spring-data cannot construct an object using that name.

mikereiche commented 5 months ago

I haven't heard back, so closing. If you would like to provide more information, you may reopen.

muhdalavu commented 5 months ago

Sorry for the delay @mikereiche , Thanks for all the clarification.

The use case is not a must but rather a good one to have. I want the document to be persisted without the package name. This will help us to re-organise the package of the class if required without modifying the underlying persisted document.

mikereiche commented 5 months ago

Spring Data needs the fully-qualified name of a class implementation to construct the object. Interfaces (which is specified in the Repository definition in your case) do not have constructors. If you are going to put something in the _class property other than the fully-qualified class name, you will need some other mechanism to provide spring data with the class name. (also be aware that _class is used in queries to select only documents that match) .

There is a test-case that is exactly what you need to do, but it involves diving deeper into spring-data customizations.

First clone the spring-data-couchbase repository. I build it first outside of the IDE to generate the querydsl test classes.

git clone git@github.com:spring-projects/spring-data-couchbase.git cd spring-data-couchbase mvn compile

Then open it in your favorite IDE.

(If you edit this file to use cluster cluster.type=unmanaged and set the seed/username/password you can run tests against against your own cb server ) https://github.com/spring-projects/spring-data-couchbase/blob/main/src/test/resources/integration.properties

cluster.type=unmanaged
...
cluster.unmanaged.seed=127.0.0.1:8091
cluster.adminUsername=Administrator
cluster.adminPassword=password

Now look at

src/test/java/org/springframework/data/couchbase/domain/OtherUser.java

It has a subtype field that is used to determine the class. As you might guess, it will also rely on an AbstractingTypeMapper.

    `this.subtype = AbstractingTypeMapper.Type.OTHERUSER;`

Then look at AbstractingTypeMapper to see how that determines the actual class name from the subtype.

CouchbaseAbstractRepositoryIntegrationTests uses AbstractingMappingCouchbaseConverter which uses AbstractingTypeMapper. So if you run those tests you will see how it all works together.

muhdalavu commented 5 months ago

Thank you for the response. I will have a look into this. Something that can be improved by a feature extension.

mikereiche commented 4 months ago

Please repen if you need more information.