Kotlin - Using data class with type from an interface - spring

I have an overrides interface which I created in order to combine 2 data classes - like so:
interface Overrides
data class SoOverrides(
val soId: String,
val freeInterval: String?
) : Overrides
data class CoOverrides(
val coId: String,
val pubType: String,
) : Overrides
I'm then trying to set the type of an item in my main data class to Overrides like so:
#Document(collection = Campaign.COLLECTION)
data class Campaign(
#Id
val id: String,
val title: String,
val overrides: List<Overrides>? = null
) {
companion object {
const val COLLECTION: String = "campaigns"
}
}
However whenever I go to run this, I get the error:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [co.....models.Overrides]: Specified class is an interface
Could someone please explain what I need to do to use that type Overrides. I'm extending the interface that way so I can have multiple classes, under one name, but not sure why it doesnt work?
I guess I could just use val overrides: List<*>? = null
Any help appreciated.
Thanks.

Related

Getting bean creation errors when creating a shared interface for my ReactiveCrudRepositories, is it possible to create share interfaces like this?

so my Kotlin / spring-boot application makes use of several reactiveCrudRepositories, and I am trying to reduce the amount of code I need to write methods that are common to each table in my database.
I was trying to create a common interface that each table implements, but am getting the following error when i build:
Error creating bean with name 'sharedRepo' defined in com...sharedRepo defined in #EnableR2dbcRepositories declared on R2dbcRepositoriesAutoConfiguireRegistrar.EnableR2dbcRepositoriesConfiguration: Invocation of ini method failed; nested exception is in org.springframework.data.mapping.MappingException: Couldn't find persistentEntity for type class java.lang.Object!
I have tried reducing the complexity of the interface down to just an empty interface (so that i can still write generic methods) but this has not worked.. the following is what my code currently looks like:
itemA.kt
#Table(table_a)
data class itemA(
#Id
#Column("id")
val menuId: UUID
#Column("name")
val prodName: String
...
}
interface AClassRepository : sharedRepo<itemA>, Serializable
itemB.kt
#Table(table_b)
data class itemB(
#Id
#Column("id")
val menuId: UUID
#Column("name")
val prodName: String
...
}
interface BClassRepository : sharedRepo<itemB>, Serializable
SharedRepo.kt
interface sharedRepo<T> : ReactiveCrudRepository<T, UUID>
databaseAssistant.kt
fun <T> sharedRepo<T>.persist(item: T) = {
Try {
//this is calling the ReactiveCrudRepository.save method
save
} ...
}
fun <T> sharedRepo<T>.someOtherDbCall(item: T, newName: String) =
*some other method body*
serviceC.kt
#Service
class ServiceC(
private val repositoryA: AClassRepository,
private val repositoryB: BClassRepository
( {
fun useRepo() =
repositoryB.persist(someInstanceOfItemA)
fun useOtherRepo() =
repositoryA.someOtherDbCall(someInstanceOfItemB, "aNiceName")
}

No value passed for parameter

I use kotli. I define everything as per requirement, why I am getting
thing type of Issue
UserRegistrationService.kt: (25, 36): No value passed for parameter 'userRegistration'
I got this type of issue at my UserRegistration class No value passed
for parameter department, and userRegistration
I Creat ResponseTemplateVO POJO Class
ResponseVO.kt
package com.userservice.userregistration.VO
import com.userservice.userregistration.entity.UserRegistration
data class ResponseTemplateVO(
var userRegistration: UserRegistration,
var department: Department
)
Department.kt
package com.userservice.userregistration.VO
data class Department(
val departmentId:Long=-1,
val departmentName:String="",
val departmentAddress:String="",
val departmentCode:String=""
)
UserRegistration.kt
package com.userservice.userregistration.entity
data class UserRegistration(
val userId:Long=-1,
val firstName:String="",
val lastName:String="",
val email:String="",
val departmentId:Long=-1,
)
UserRegistrationService.kt
package com.userservice.userregistration.service
import com.userservice.userregistration.VO.Department
import com.userservice.userregistration.VO.ResponseTemplateVO
import com.userservice.userregistration.entity.UserRegistration
import com.userservice.userregistration.repository.UserRegistrationRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
#Service
class UserRegistrationService {
#Autowired
private lateinit var userRegistrationRepository: UserRegistrationRepository
#Autowired
private lateinit var restTemplate: RestTemplate
fun saveUserDetails(userRegistration: UserRegistration): UserRegistration {
return userRegistrationRepository.save(userRegistration)
}
fun getUserWithDepartment(userId: Long): ResponseTemplateVO {
val vo= ResponseTemplateVO()
val userRegistration:UserRegistration=userRegistrationRepository.findUserById(userId)
val department: Department? =
restTemplate.getForObject("http://localhost:9001/departments/"+userRegistration.departmentId,
Department::class.java)
vo.userRegistration=userRegistration
if (department != null) {
vo.department=department
}
return vo
}
}
I am getting error at this below method at the line 2
val vo= ResponseTemplateVO()
No value passed for parameter department and userRegistration .This is
the error
fun getUserWithDepartment(userId: Long): ResponseTemplateVO {
val vo= ResponseTemplateVO()
val userRegistration:UserRegistration=userRegistrationRepository.findUserById(userId)
val department: Department? =
restTemplate.getForObject("http://localhost:9001/departments/"+userRegistration.departmentId,
Department::class.java)
vo.userRegistration=userRegistration
if (department != null) {
vo.department=department
}
return vo
}
This declaration:
data class ResponseTemplateVO(
var userRegistration: UserRegistration,
var department: Department
)
packs multiple things:
it declares 2 properties userRegistration and department
it defines the primary constructor of the class with 2 arguments: userRegistration and department
When you write:
val vo = ResponseTemplateVO()
You're calling the constructor of that class, but you don't specify the 2 required arguments. You should instead call it by passing the arguments:
fun getUserWithDepartment(userId: Long): ResponseTemplateVO {
val userRegistration:UserRegistration=userRegistrationRepository.findUserById(userId)
val department: Department? = restTemplate.getForObject("http://localhost:9001/departments/"+userRegistration.departmentId,
Department::class.java)
if (department == null) {
// here you should decide if it should have a default value
// or throw an exception
}
return ResponseTemplateVO(userRegistration, department)
}
Note that you declared the department property as non-null, so you need a non-null department in order to create an instance of your class.
So if department is null you have 3 options:
throw an exception
use a default value instead
change the type of department in ResponseTemplateVO so it accepts nulls (Department? with ?)
Also, if you instantiate your class with all required value like that, and you don't need to modify its properties later, the properties can be declared val. This is usually more idiomatic Kotlin. With immutability, it's easier to reason about the values.
The issue is in the data class.
data class ResponseTemplateVO(
var userRegistration: UserRegistration,
var department: Department
)
Here you have added the following params into the constructor of the data class. Hence you will need to pass the values to the constructor of the class before you can initialise it.
Hence your ResponseTemplateVO data class will become like this
data class ResponseTemplateVO(
var userRegistration: UserRegistration?=null,
var department: Department?=null)
Now since we have already assigned null as the default value. Now you can initialise the data class and it creates the data class with the values set to null and you do not need to pass any value for params to the constructor. Now you can access each of the variables and set the respective data into them.

Neo4J (spring boot) with sealed classes in kotlin

I'm trying to create a somewhat generic implementation using kotlin and neo4j. My idea right now is that I want to have a GeoNode that can point to any kind of GeoJson feature (e.g. "Feature" or "FeatureCollection")
I tried to do this using kotlins sealed classes, e.g.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
#JsonSubTypes(
JsonSubTypes.Type(value = GeoJsonFeature::class, name = "GeoJsonFeature")
)
#Node
sealed class FeatureContract(
#GeneratedValue #Id var id: Long? = null,
#Version var version: Long? = null
) {
companion object {
#JsonCreator
#JvmStatic
private fun creator(name: String): FeatureContract? {
return FeatureContract::class.sealedSubclasses.firstOrNull {
it.simpleName == name
}?.objectInstance
}
}
}
data class GeoJsonFeature(
val geometry: GeometryContract,
) : FeatureContract()
data class GeoJsonFeatureCollection(
val features: List<GeoJsonFeature>,
) : FeatureContract()
// and the "GeoNode" that holds this
#Node
data class GeoNode(
#Id val id: String,
#Version var version: Long? = null
#Relationship(type = "feature") var feature: FeatureContract?, // Should be either a featureCollection or a feature,
)
The idea is that I can have a point on a map that points to any kind of GeoJson.
It seems I am successful in serializing this and getting it into the Neo4J-db on write, however, on reading I get
Failed to instantiate [FeatureContract]: Is it an abstract class?; nested exception is java.lang.InstantiationException
The Jackson annotations are there cause I hoped they would help me (that Neo4J-OGM was using it under the hood) but it doesn't seem to have done the trick. I've read about Neo4JEntityConverters but I haven't understood how one can to this for a full object like this. Is there any good way to use sealed classes in kotlin with the neo4j-OGM for both serialization and deserialization?
Using spring boot 2.4.4 and spring-boot-starter-data-neo4j

How to tell Spring Data MongoDB to store nested fields of a document that are also documents in their own collection?

I have two collections called persons and addresses. The idea is to have person hold an address in the field address. I use Spring Data MongoDB to persist those mentioned documents.
My usual way of crafting the "relation" between Person > Address was to store the ID of the address and give it to the person object. Later when I find() a person I resolve the address object by it's id and voila I have my person + address.
However I find this somewhat every cumbersome since in my code I just want to add the Address object as whole and not only it's ID so I can work with it while also saving it to the repository at any point of time.
I therefore started a little unit test to see how Spring Data MongoDB saves the Address object if it's just a field of Person and is not saved by it's own Repository.
This is what I came up with:
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Repository
#Document("person")
data class Person(
val id: String,
val name: String,
val age: Int,
var address: Address
)
#Document("addresses")
data class Address(
val id: String,
val street: String?,
val number: Int?
)
#Repository
interface PersonRepository : MongoRepository<Person, String>
#Repository
interface AddressRepository : MongoRepository<Address, String>
And this is the unit test - that fails with the last steps as I was expecting:
internal class FooTest #Autowired constructor(
private val personRepository: PersonRepository,
private val addressRepository: AddressRepository
) {
#Test
fun `some experiment`() {
val testPerson = Person("001", "Peter", 25, Address("011","Lumberbumber", 12))
personRepository.save(testPerson)
val person = personRepository.findAll()[0]
assertThat(person).isNotNull
assertThat(person.address).isNotNull
assertThat(person.address.street).isEqualTo("Lumberbumber")
assertThat(person.address.number).isEqualTo(12)
// works because address was just copied into the object structure
// of `person` and was not seen as a standalone document
val address = addressRepository.findAll()[0]
assertThat(address.street).isEqualTo("Lumberbumber") // fails
assertThat(address.number).isEqualTo(12) // fails
// As expected `address` was not persisted alongside the `person` document.
}
}
So I thought about using AbstractMongoEventListener<Person> to intercept the saving process and pick the Address object out from Person here and do a addressRepository.save(addressDocument) while putting a lightweight address object (only having the ID) back in the Person document.
The same I'd to in the reverse when doing a find for Person and assembling Person and Address together again.
#Component
class MongoSaveInterceptor(
val addressRepository: AddressRepository
) : AbstractMongoEventListener<Person>() {
override fun onBeforeConvert(event: BeforeConvertEvent<Person>) {
val personToSave = event.source
val extractedAddress = personToSave.address
val idOfAddress = addressRepository.save(extractedAddress).id
personToSave.address = Address(idOfAddress, null, null)
}
override fun onAfterConvert(event: AfterConvertEvent<Person>) {
val person = event.source
val idOfAddress = person.address.id
val foundAddress = addressRepository.findById(idOfAddress)
foundAddress.ifPresent {
person.address = it
}
}
}
It works that way and might be a workaround solution for my requirement.
BUT
I feel that there has to be something like that already working and I might just need to find the proper configuration for that.
That's where I am stuck atm and need some guidance.
Another research showed me it's about #DBRef (https://www.baeldung.com/cascading-with-dbref-and-lifecycle-events-in-spring-data-mongodb) I have to use. This way Spring Data MongoDB stores the embedded document class and resolves it when loading the parent document object from the database.

Attempted duplicate class definition exception when inserting data class to MongoDB

I'm doing some Spring Data document insertions in scheduled task, like this:
val session = client.startSession()
val template = MongoTemplate(client, db)
val sessionBoundOps = template.withSession(session)
session.startTransaction()
sessionBoundOps.insert(obj);
session.commitTransaction()
Which leads to the following exception:
Unexpected error occurred in scheduled task
java.lang.RuntimeException: java.lang.IllegalStateException: org.springframework.cglib.core.CodeGenerationException: java.lang.LinkageError-->loader org.springframework.boot.loader.LaunchedURLClassLoader #5d3411d attempted duplicate class definition for pw.prj.core.domain.stats.UserDailyStats_Accessor_wvhcc8. (pw.prj.core.domain.stats.UserDailyStats_Accessor_wvhcc8 is in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader #5d3411d, parent loader 'app')
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.createAccessorClass(ClassGeneratingPropertyAccessorFactory.java:200)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.potentiallyCreateAndRegisterPersistentPropertyAccessorClass(ClassGeneratingPropertyAccessorFactory.java:184)
at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.getPropertyAccessor(ClassGeneratingPropertyAccessorFactory.java:92)
at org.springframework.data.mapping.model.BasicPersistentEntity.getPropertyAccessor(BasicPersistentEntity.java:455)
at org.springframework.data.mapping.model.IdPropertyIdentifierAccessor.<init>(IdPropertyIdentifierAccessor.java:54)
at org.springframework.data.mapping.model.BasicPersistentEntity.getIdentifierAccessor(BasicPersistentEntity.java:471)
at org.springframework.data.mongodb.core.EntityOperations$AdaptibleMappedEntity.of(EntityOperations.java:619)
at org.springframework.data.mongodb.core.EntityOperations$AdaptibleMappedEntity.access$100(EntityOperations.java:598)
at org.springframework.data.mongodb.core.EntityOperations.forEntity(EntityOperations.java:104)
at org.springframework.data.mongodb.core.MongoTemplate.doInsert(MongoTemplate.java:1244)
at org.springframework.data.mongodb.core.MongoTemplate.insert(MongoTemplate.java:1185)
at org.springframework.data.mongodb.core.MongoTemplate.insert(MongoTemplate.java:1170)
at pw.prj.core.infra.persistence.MongoUserDailyStatsPersister$saveStatsAndTransferMoney$1.invoke(MongoUserDailyStatsPersister.kt:44)
at pw.prj.core.infra.persistence.MongoUserDailyStatsPersister$saveStatsAndTransferMoney$1.invoke(MongoUserDailyStatsPersister.kt:18)
at pw.prj.core.infra.TransactionRunner.run(TransactionRunner.kt:20)
...
My model is the simple data class, looks like this:
#Document
#CompoundIndexes(value = [
CompoundIndex(def = "{'userId': 1, 'date': 1}", unique = true)
])
data class UserDailyStats(
val userId: ObjectId,
val date: Date,
val viewsPerOwnServer: Map<String, Int>,
val adViewsPerOwnServer: Map<String, Int>,
val revenuePerOwnServer: Map<String, String>,
val referralRegistrations: Int,
val referralViews: Int,
val referralAdViews: Int,
val referralRevenue: String,
#Id val id: String? = null
)
Java 12, Spring Boot 2.2.0.M5. Do you have any idea what's going wrong? Thanks in advance.
What helped me is to use common class instead of data class. Also changed id field type to var because of Spring complaints about final property.
Update: Spring Boot 2.2.6.RELEASE fixes this as well, so it's still possible to use data classes for entities in multi-connection Spring environments.

Resources