I want inject a singleton in another class by kotlin in spring boot.
S.kt
#Singleton
#Component
class S(
private val userService: UserService,
val companyRepo: CompanyRepo
)
WorkingGroup.kt
class WorkingGroup(
override val name: String = "",
override val desc: String = ""
) : Csv() {
fun isCompatible(ct2: WorkingGroup): Boolean = this == ct2
companion object : ICsvEnumCompanion<WorkingGroup> {
#Inject
private lateinit var s: S
override val VALUES: List<WorkingGroup>
by lazy {
val details = s.user().company.details ?: CompanyDetails()
details.workingGroups.map { WorkingGroup(it.name, it.desc) }
}
}
}
By this code, I get below error:
Caused by: org.zalando.problem.DefaultProblem: Internal Server Error: lateinit property s has not been initialized
I search for this error and found some result like this, but the problem not solved.
How can I inject service in companion object in kotlin?
In order for Spring to inject into a companion object you will need to create a setter for the field outside of the companion object. WorkingGroup will need to be a Spring managed bean in order for Spring to autowire it (inject dependencies).
#Component
class WorkingGroup(
override val name: String = "",
override val desc: String = ""
) : Csv() {
fun isCompatible(ct2: WorkingGroup): Boolean = this == ct2
companion object : ICsvEnumCompanion<WorkingGroup> {
private lateinit var s: S
override val VALUES: List<WorkingGroup>
by lazy {
val details = s.user().company.details ?: CompanyDetails()
details.workingGroups.map { WorkingGroup(it.name, it.desc) }
}
}
#Autowired
fun setS(value: S) {
s = value;
}
}
Related
#Service
public class AvailablePolicyService {
#Autowired
private var availablePolicyRepository : AvailablePolicyRepository = **AvailablePolicyRepository()**
fun saveAvailablePolicy(availablePolicy: AvailablePolicy): AvailablePolicy { return availablePolicyRepository.save(availablePolicy) }
fun getAllAvailablePolicy(): List<AvailablePolicy>{ return availablePolicyRepository.findAll() }
fun getAvailablePolicyByPolicyId(policyId: String?): AvailablePolicy? {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyId().equals(policyId)) {
return availablePolicy
}
}
return null
}
fun getAvailablePolicyByPolicyCategory(policyCategory: String?): ArrayList<AvailablePolicy> {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
var availablePolicyCategory = ArrayList<AvailablePolicy>()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyCategory().equals(policyCategory)) {
availablePolicyCategory.add(availablePolicy)
}
}
return availablePolicyCategory
}
}
#Repository
interface AvailablePolicyRepository : MongoRepository<AvailablePolicy, String>
The bolded text shows where the error is showing and it reads "Interface AvailablePolicyRepository does not have constructors". How do I initialize repository from service?
How do I initialize repository from service?
That's the thing. You don't! Spring does it for you:
#Autowired
private lateinit var availablePolicyRepository: AvailablePolicyRepository
Field injection is rather obsolete and you should consider using contructor injection instead.
#Service
class AvailablePolicyService(private val availablePolicyRepository: AvailablePolicyRepository) {...}
It's another one of those circular reference errors in Spring Boot. My log says:
userDetailsServiceImpl defined in file [/home/.../auth/UserDetailsServiceImpl.class]
┌─────┐
| tenantDataSource
└─────┘
Now, I've seen this happening to other people, but never with one class. Here comes the TenantDataSource:
#Component
class TenantDataSource(private val configRepository: DataSourceConfigRepository): Serializable {
private val dataSources = HashMap<String, DataSource>()
val hexKeyMaterials = HashMap<String, String>()
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
fun getDataSource(dataSourceName: String): DataSource {
logger.info(" [X] TenantDataSource - Getting DataSource: $dataSourceName")
if (!dataSources.contains(dataSourceName)) {
logger.info(" [X] TenantDataSource - DataSource $dataSourceName is not in map... Creating...")
dataSources[dataSourceName] = createDataSource(dataSourceName)
}
return dataSources[dataSourceName]!!
}
#PostConstruct
fun getAll(): Map<String, DataSource> {
logger.info(" [X] TenantDataSource - Processing all datasources after construct")
val configList: List<DataSourceConfig> = configRepository.findAll()
val result: MutableMap<String, DataSource> = HashMap()
for (config in configList) {
result[config.name] = getDataSource(config.name)
hexKeyMaterials[config.name] = config.keymaterial
}
return result
}
private fun createDataSource(dataSourceName: String): DataSource {
logger.info(" [X] TenantDataSource - Creating dataSource from config: $dataSourceName")
val config: DataSourceConfig = configRepository.findByName(dataSourceName)
return DataSourceBuilder
.create()
.username(config.username)
.password(config.password)
.url(config.url).build()
}
}
I can't imagine where I should start looking for the circular reference in this... :( I've seen some people saying "i don't even have constructor injection..." Well, I do because I don't like the autowired lateinit variables in Kotlin, that's all. So if there's a chance I'd like to keep my CIs. Help me out here because I don't get what's going on! :)
Oh, the UserDetailsServiceImpl if it helps:
#Service
class UserDetailsServiceImpl(private val userRepository: UserRepository, private val decrypter: HaliteDecrypter, private val tenantDataSource: TenantDataSource): UserDetailsService {
private val logger: Logger = LoggerFactory.getLogger(this.javaClass)
override fun loadUserByUsername(username: String): AuthUser {
logger.info("Trying to get user for authentication $username")
val user = userRepository.getUserByUsername(username)
decrypter.hexKeyMaterial = tenantDataSource.hexKeyMaterials[TenantContext.currentTenant.get()]!!
user?.let {
logger.info("Returning UserDetails for " + user.username)
return AuthUser(
user.userId,
user.username,
decrypter.decryptPasswd(user.password).toString(Charsets.US_ASCII),
user.role.id,
user.role.name,
user.flags
)
} ?: throw UsernameNotFoundException(String.format("Username %s not found", username))
}
}
This is the snippet of my test class:
#SpringBootTest
#ExtendWith(MockitoExtension::class)
class ResourceSearchServiceTest {
#Mock
lateinit var resourceSearchRepository: ResourceSearchRepository
#InjectMocks
lateinit var resourceSearchService: ResourceSearchService
lateinit var expectedResourceMetadata : ResourceSearchMetadata
#BeforeEach
fun createResourceMetadata() {
expectedResourceMetadata =
ResourceSearchMetadata(
id = UUID.randomUUID(),
pk = UUID.randomUUID(),
metadata = ResourceMetadata(
description = "blabla"
)
)
}
#Test
fun activeResourceMetadataTest() {
Mockito.`when`(resourceSearchRepository.findById(any(UUID::class.java))).thenReturn(Mono.just(expectedResourceMetadata))
Mockito.`when`(resourceSearchRepository.save(any(ResourceSearchMetadata::class.java))).thenReturn(Mono.just(expectedResourceMetadata))
resourceSearchService.updateActiveStatus(expectedResourceMetadata.id, false)
Mockito.verify(resourceSearchRepository, Mockito.times(1)).save(expectedResourceMetadata)
assertThat(expectedResourceMetadata.metadata.active).isFalse
}
}
and this is my service :
#Service
class ResourceSearchService(private val resourceSearchRepository: ResourceSearchRepository) {
fun updateActiveStatus(id: UUID, isActive: Boolean) {
resourceSearchRepository.findById(id).flatMap { resource ->
resource.metadata.active = isActive
resourceSearchRepository.save(resource)
}.map { Success }.defaultIfEmpty(NotFound)
}
}
when updateAciveStatus service method is called from the test class a nullpointer exception is raised by the repository at this line : resourceSearchRepository.findById(id)
It's like the mocked repository is not injected into the service.
Any idea what's wrong in my Kotlin code ?
I have the next structure of spring beans
abstract class GenericRepository<T> {
private val FIND_BY_ID_SQL = "SELECT * FROM ${this.getTableName()} WHERE id = ?"
abstract fun getTableName(): String
abstract fun jdbcTemplate(): JdbcTemplate
abstract fun getMapper(): RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate().queryForObject(FIND_BY_ID_SQL, arrayOf(id), getMapper())
}
}
User repository
#Repository
class UserRepository(
#Autowired
private val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>() {
companion object {
private const val INSERT_SQL = "INSERT INTO \"user\"(name, age) VALUES (?,?)"
}
private class LogMapper : RowMapper<User> {
override fun mapRow(rs: ResultSet, rowNum: Int): User? {
return User(
id = rs.getLong("id"),
name = rs.getString("name"),
age = rs.getInt("operation")
)
}
}
override fun getTableName(): String {
return "user"
}
override fun jdbcTemplate(): JdbcTemplate {
return jdbcTemplate
}
override fun getMapper(): RowMapper<User> {
return LogMapper()
}
}
The problem when Spring creates proxy and creates bean of UserRepository it doesn't initialize FIND_BY_ID_SQL leaving it null.
The question: how usign abstract class make spring initialize FIND_BY_ID_SQL variable?
UPD
I used #Component instead of #Repository and the problem was solved. FIND_BY_ID_SQL is not null anymore.
You could work around the problem by making it lazy:
private val FIND_BY_ID_SQL by lazy { "SELECT * FROM ${this.getTableName()} WHERE id = ?" }
However, you should first be sure it's an actual problem (e.g. that when you call find you get an exception), because the proxy might simply delegate to a "real" UserRepository with non-null FIND_BY_ID_SQL (and jdbcTemplate etc.), depending on Spring's internal details.
In addition, you need to be careful when your superclass properties are initialized depending on subclass; I think your exact situation should work, but I'd prefer to write it as
abstract class GenericRepository<T>(val tableName: String) {
private val FIND_BY_ID_SQL = "SELECT * FROM ${tableName} WHERE id = ?"
abstract val jdbcTemplate: JdbcTemplate
abstract val mapper: RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate.queryForObject(FIND_BY_ID_SQL, arrayOf(id), mapper)
}
}
#Repository
class UserRepository(
#Autowired
override val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>("user") { ... }
I've defined a Neo4j repository as follow (code is in Kotlin but it's very close to Java) :
#Repository
interface UserRepository : GraphRepository<User> {
fun findByEmail(email: String): User?
#Query("match (n:User)-[:IS_AUTH]->(:Permission {name: {0}}) where id(n) = {1} return n")
fun authorizedUser(permission: String, userId: Long): User?
}
In a controller, I've written:
#Controller
#RequestMapping("/company")
open class CreateCompanyController {
#Autowired private lateinit var userRepo: UserRepository
#RequestMapping(method = arrayOf(RequestMethod.POST))
#ResponseBody
fun createCompany(#RequestParam(value = "name", required = true) name: String,
#RequestParam(value = "siret", required = true) siret: Long) : ResponseEntity<String> {
val connectedUser = SecurityContextHolder.getContext().authentication.principal as User
val testUser = userRepo.findByEmail("test#company.com")
if (!PermissionManager().hasAuthorizationFor(PermissionManager.ADMIN_ALL, connectedUser))
return ResponseEntity("You're not authorized to create a company", HttpStatus.FORBIDDEN)
return ResponseEntity(HttpStatus.OK)
}
}
In the code above, the userRepo property is set well (aka. its value is not null).
I want to have this UserRepository in a custom service. So, I've coded:
#Service
open class PermissionManager {
companion object {
val ADMIN_ALL = "ADMIN_ALL"
}
#Autowired private lateinit var userRepo: UserRepository
fun hasAuthorizationFor(permissionName: String, user: User): Boolean {
return userRepo.authorizedUser(permissionName, user.id!!) != null
}
}
But in this case, the userRepo property is not initialized.
I've written the PersistenceContext class as:
#Configuration
#ComponentScan("persistence")
#EnableNeo4jRepositories("persistence.repositories")
#EnableTransactionManagement
open class PersistenceContext : Neo4jConfiguration() {
#Bean override fun getSessionFactory(): SessionFactory {
return SessionFactory("persistence.domain")
}
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
open override fun getSession(): Session {
return super.getSession()
}
}
What I don't understand is why is the userRepo set in my controller but not in my service ?