Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add comprehensive Spring integration example and enhance DataFrame pr…
…ocessing logic

This commit introduces a new detailed Spring-style integration example (`SpringIntegrationExample2.kt`), showcasing advanced usage patterns and GitHub issue resolution (#1321). Updates also include improvements in DataFrame field injection logic to handle enhanced annotation processing, robust property checks, and better fallback mechanisms for ApplicationContext. Additionally, minor tweaks enable broader compatibility and extensibility within the Spring ecosystem.
  • Loading branch information
zaleslaw committed Aug 22, 2025
commit b74f60de9e1b09478dbf157777d7c92fbb53d97e
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import org.springframework.stereotype.Component
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaGetter
import org.springframework.context.support.StaticApplicationContext

/**
* Spring BeanPostProcessor that automatically populates DataFrame fields
Expand Down Expand Up @@ -48,7 +49,8 @@ import kotlin.reflect.jvm.javaField
@Component
class DataFramePostProcessor : BeanPostProcessor, ApplicationContextAware {

private lateinit var applicationContext: ApplicationContext
// Make context optional to support both Spring-managed and manual usage
private var applicationContext: ApplicationContext? = null

private val processors = mapOf<Class<out Annotation>, DataSourceProcessor>(
CsvDataSource::class.java to CsvDataSourceProcessor(),
Expand All @@ -74,23 +76,36 @@ class DataFramePostProcessor : BeanPostProcessor, ApplicationContextAware {
}

private fun processProperty(bean: Any, prop: KProperty1<out Any, *>, beanName: String) {
// Check if the property is a DataFrame type
// Skip non-DataFrame properties
if (!isDataFrameProperty(prop)) {
return
}

// Get the Java field for reflection access
val field = prop.javaField ?: return
// Obtain reflection handles
val field = prop.javaField
val getter = prop.javaGetter

// Try each supported annotation type
// Try each supported annotation and search on property/getter/field
for ((annotationType, processor) in processors) {
val annotation = field.getAnnotation(annotationType) ?: continue
val fromProperty = prop.annotations.firstOrNull { annotationType.isInstance(it) }
val fromGetter = getter?.getAnnotation(annotationType)
val fromField = field?.getAnnotation(annotationType)

val annotation = (fromProperty ?: fromGetter ?: fromField) ?: continue

try {
val dataFrame = processor.process(annotation, applicationContext)
field.isAccessible = true
field.set(bean, dataFrame)
return // Successfully processed, don't try other annotations
// Use provided ApplicationContext if available; otherwise fallback to a lightweight static context
val ctx = applicationContext ?: StaticApplicationContext()
val dataFrame = processor.process(annotation, ctx)

// Inject into backing field
val targetField = field ?: prop.javaField
?: throw IllegalStateException(
"No backing field found for property '${prop.name}' in bean '$beanName' to inject DataFrame"
)
targetField.isAccessible = true
targetField.set(bean, dataFrame)
return // Successfully processed, stop trying other annotations
} catch (e: Exception) {
throw RuntimeException(
"Failed to process ${annotationType.simpleName} annotation for property '${prop.name}' in bean '$beanName'",
Expand All @@ -101,8 +116,8 @@ class DataFramePostProcessor : BeanPostProcessor, ApplicationContextAware {
}

private fun isDataFrameProperty(prop: KProperty1<out Any, *>): Boolean {
val returnType = prop.returnType
val classifier = returnType.classifier
return classifier == DataFrame::class
// Robust check that works for parameterized DataFrame<T>
val classifier = prop.returnType.classifier as? kotlin.reflect.KClass<*> ?: return false
return DataFrame::class.java.isAssignableFrom(classifier.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ class DataAnalysisService {
* Spring configuration that enables the DataFramePostProcessor
*/
@Configuration
class DataFrameConfiguration {
open class DataFrameConfiguration {

@Bean
fun dataFramePostProcessor(): DataFramePostProcessor {
open fun dataFramePostProcessor(): DataFramePostProcessor {
return DataFramePostProcessor()
}
}
Expand Down Expand Up @@ -158,4 +158,4 @@ private fun cleanupSampleData() {
File("customers.csv").delete()
File("sales.csv").delete()
println("Sample data cleaned up.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.jetbrains.kotlinx.dataframe.spring.examples

import org.jetbrains.kotlinx.dataframe.spring.DataFramePostProcessor
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import java.io.File

/**
* Demonstration of the complete Spring-style integration
*/
fun main() {
println("=== DataFrame Spring Integration Demo ===")
println("Demonstrating exact usage pattern from GitHub issue #1321")
println()

// Create sample data file
createSampleDataFile()

try {
println("1. Bootstrapping Spring context...")
val ctx = AnnotationConfigApplicationContext().apply {
register(DataFramePostProcessor::class.java)
register(MyDataService::class.java)
refresh()
}

println("2. Getting MyDataService bean from context...")
val myDataService = ctx.getBean(MyDataService::class.java)

println("3. DataFrame loaded successfully!")
println(" - CSV file: data.csv")
println(" - Rows loaded: ${myDataService.df.rowsCount()}")
println(" - Columns: ${myDataService.df.columnNames()}")

println("4. Running business logic...")
myDataService.process()

println()
println("✅ SUCCESS: Spring-style DataFrame initialization completed!")
println("✅ The @DataSource annotation automatically loaded CSV data")
println("✅ No manual DataFrame construction required")
println("✅ Follows Spring DI patterns perfectly")

} catch (e: Exception) {
println("❌ ERROR: ${e.message}")
e.printStackTrace()
} finally {
// Clean up
File("data.csv").delete()
}
}

/**
* Creates the sample CSV file used in the example
*/
private fun createSampleDataFile() {
File("data.csv").writeText("""
id,name,value
1,First Item,100.5
2,Second Item,200.0
3,Third Item,150.75
4,Fourth Item,300.25
""".trimIndent())

println("Created sample data.csv file")
}