Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autowire Spring beans #21

Open
shrralis opened this issue Oct 12, 2023 · 3 comments
Open

Autowire Spring beans #21

shrralis opened this issue Oct 12, 2023 · 3 comments

Comments

@shrralis
Copy link

Hey!

It's more a question or feature request rather than an issue.

Is there an opportunity to use Spring Beans within Kovert converters?

Example code:

data class Entity(
    var id: Long,
    var nested: NestedEntity,
)

data class NestedEntity(
    var id: Long,
)

data class DTO(
    var id: Long,
    var nested: NestedDTO?,
    var nestedId: Long?,
)

data class NestedDTO(
    var id: Long,
)

@Konverter
@KComponent
interface EntityConverter {

    // here `dto.nested` is always `null`
    // instead, we want to use `dto.nestedId` to get an entity via `nestedRepository.findById(dto.nestedId)`
    fun convertToEntity(dto: DTO): Entity

    // here we want to fill the target `nested` using another Konverter,
    // so it would be `nestedConverter.convertToDTO(entity.nested)`
    fun convertToDTO(entity: Entity): DTO
}

So... I wonder, would it be the right approach to write something like this?

@Konverter
@KComponent
abstract class EntityConverter(
    // these should be `@Autowired` since the whole `EntityConverter` would be a `@Component`
    protected val nestedRepository: NestedRepository,
    protected val nestedConverter: NestedConverter,
) {

    @Konvert(
        mappings = [
            Mapping(target = "nested", expression = "nestedRepository.findById(it.nestedId)"),
        ]
    )
    fun convertToEntity(dto: DTO): Entity

    @Konvert(
        mappings = [
            Mapping(target = "nested", expression = "nestedConverter.convertToDTO(it.nested)"),
        ]
    )
    fun convertToDTO(entity: Entity): DTO
}

I was considering using the TypeConverter for that but I'm not sure if I would be able to achieve what I need with that.

@shrralis
Copy link
Author

shrralis commented Oct 12, 2023

Well, I've just tried my assumption and got an error:

[ksp] java.lang.IllegalStateException: Mapping can only target interfaces
	at io.mcarle.konvert.processor.konvert.KonverterDataCollector$collect$1.invoke(KonverterDataCollector.kt:19)
	at io.mcarle.konvert.processor.konvert.KonverterDataCollector$collect$1.invoke(KonverterDataCollector.kt:16)
	at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:210)
	at kotlin.sequences.SequencesKt___SequencesKt.toList(_Sequences.kt:816)
	at io.mcarle.konvert.processor.konvert.KonverterDataCollector.collect(KonverterDataCollector.kt:34)
	at io.mcarle.konvert.processor.KonvertProcessor.collectDataForAnnotatedConverters(KonvertProcessor.kt:101)
	at io.mcarle.konvert.processor.KonvertProcessor.access$collectDataForAnnotatedConverters(KonvertProcessor.kt:24)
	at io.mcarle.konvert.processor.KonvertProcessor$process$1.invoke(KonvertProcessor.kt:34)
	at io.mcarle.konvert.processor.KonvertProcessor$process$1.invoke(KonvertProcessor.kt:33)
	at io.mcarle.konvert.converter.api.config.ConfigurationKt.withIsolatedConfiguration(Configuration.kt:41)
	at io.mcarle.konvert.processor.KonvertProcessor.process(KonvertProcessor.kt:33)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$8$1.invoke(KotlinSymbolProcessingExtension.kt:305)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$8$1.invoke(KotlinSymbolProcessingExtension.kt:303)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:409)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:303)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:88)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:256)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:42)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:115)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:247)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:87)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:47)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:168)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:100)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:46)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1497)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:577)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

I also tried this approach:

@Konverter
@KComponent
interface EntityConverter {

    @set:Autowired
    var nestedConverter: NestedConverter

    @set:Autowired
    var nestedRepository: NestedRepository
}

But it also doesn't not work, result Konverter doesn't implement the abstract property, I've expected to see there something like

object EntityConverterImpl : EntityConverter {

    override lateinit var nestedConverter: NestedConverter

    override lateinit var nestedRepository: NestedRepository
    ....
}

@mcarleio
Copy link
Owner

Hi @shrralis, right now using other beans is not directly possible with Konvert. You can use workarounds (see below), but, you should really think about if you want to include that kind of logic into mapping code. From my experience, mapping code should be as simple as possible.

The entity to DTO direction is already possible without any extra effort, just define a mapping function, Konvert will use that function automatically. This code snippet should be enough for your example:

@Konverter
interface EntityConverter {
    fun convertToDTO(entity: Entity): DTO
    fun convertToDTO(nestedEntity: NestedEntity): NestedDTO
}

Regarding the workaround for the other way around where you need a bean, you can e.g. access the application context:

  1. Declare e.g. an ApplicationContextAware bean and store the context in the companion object.
@Component
class YourApplicationAwareBean : ApplicationContextAware {
    companion object {
        lateinit var applicationContext: ApplicationContext
    }

    override fun setApplicationContext(applicationContext: ApplicationContext) {
        YourApplicationAwareBean.applicationContext = applicationContext
    }
}
  1. In your mapper interface, you can create a function to load it:
@Konverter
interface EntityConverter {

    @Konvert(
        mappings = [
            Mapping(target = "nested", expression = "findById(it.nestedId)"),
        ]
    )
    fun convertToEntity(dto: DTO): Entity

    fun findById(nestedId: Long?): NestedEntity? {
      return YourApplicationAwareBean.applicationContext.getBean(NestedRepository::class.java).findById(nestedId)
    }

}

Not the best solution, but depending on your context this might work well enough.

@shrralis
Copy link
Author

shrralis commented Oct 15, 2023

@mcarleio thanks for the workaround!
I hope someday it will be possible to annotate abstract class with @Konverter (but that would probably require Konvert to create classes rather than objects) or if Konvert will be implementing abstract var as lateinit var 🙃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants