Using ConnectableFlux for hot stream REST endpoint - spring

I'm trying to create a REST endpoint to subscribe to a hot stream using Reactor.
My test provider for the stream looks like this:
#Service
class TestProvider {
fun getStream(resourceId: String): ConnectableFlux<QData> {
return Flux.create<QData> {
for (i in 1..10) {
it.next(QData(LocalDateTime.now().toString(), "next"))
Thread.sleep(500L)
}
it.complete()
}.publish()
}
}
My service for the REST endpoint looks like this:
#Service
class DataService #Autowired constructor(
private val prv: TestProvider
) {
private val streams = mutableMapOf<String, ConnectableFlux<QData>>()
fun subscribe(resourceId: String): Flux<QData> {
val stream = getStream(resourceId)
return Flux.push { flux ->
stream.subscribe{
flux.next(it)
}
flux.complete()
}
}
private fun getStream(resourceId: String): ConnectableFlux<QData> {
if(streams.containsKey(resourceId).not()) {
streams.put(resourceId, createStream(resourceId))
}
return streams.get(resourceId)!!
}
private fun createStream(resourceId: String): ConnectableFlux<QData> {
val stream = prv.getStream(resourceId)
stream.connect()
return stream
}
}
The controller looks like this:
#RestController
class DataController #Autowired constructor(
private val dataService: DataService
): DataApi {
override fun subscribe(resourceId: String): Flux<QData> {
return dataService.subscribe(resourceId)
}
}
The API interface looks like this:
interface DataApi {
#ApiResponses(value = [
ApiResponse(responseCode = "202", description = "Client is subscribed", content = [
Content(mediaType = "application/json", array = ArraySchema(schema = Schema(implementation = QData::class)))
])
])
#GetMapping(path = ["/subscription/{resourceId}"], produces = [MediaType.APPLICATION_JSON_VALUE])
fun subscribe(
#Parameter(description = "The resource id for which quality data is subscribed for", required = true, example = "example",allowEmptyValue = false)
#PathVariable("resourceId", required = true) #NotEmpty resourceId: String
): Flux<QData>
}
Unfortunately, my curl delivers an empty array.
Does anyone has an idea what the issue is? Thanks in advance!

I had to run connect() async in DataService:
CompletableFuture.runAsync {
stream.connect()
}

Related

How to initialize repository from service in Springboot Kotlin

#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) {...}

How to combine key and value using Jackson (Spring boot)

I have json like below.
{
"USER0001": {
"name": "hoge",
"age": 20
},
"USER0002": {
"name": "huga",
"age": 10
}
}
and, this is my User data class.
data class User(
val id: String,
val name: String,
val age: Int
)
then, I want to convert json to user list when request is send controller.
listOf(
User("USER0001", "hoge", 20),
User("USER0002", "huga", 10),
)
and my controller .
#RestController
class MyController() {
fun test(#RequestBody users: List<User>) {
// some code. I want to use users as List<User>
}
}
I try using #JsonComponent like below,
class Deserializer : JsonDeserializer<List<User>>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): List<User> {
val treeNode = parser.codec.readTree<TreeNode>(parser)
val fieldNames = treeNode.fieldNames()
val result = mutableListOf<User>()
while(fieldNames.hasNext()) {
val fieldName = fieldNames.next()
val userJson = treeNode.get(fieldName)
// I can't use this code as String type.
val name = userJson.get("name")
// How Can I make User model ???
}
return result
}
}
then, I don't know how to make User object in deserializer method.
do you know how to do this ?
thank you reading.
this is simple way.
#RestController
class MyController() {
fun test(#RequestBody Map<String, UserDetail>) {
// some code...
}
}

How to get a data from mongoDB with the _id in Kotlin

I'm new in programming and I'm had having some problems to create an API for Kotlin getting data from a existing DB from mongoDB to my API.
I'm making Tests and I want to return a data with it Id, but I can't.
I'm using the SpringBoot, JPA, and mongoDB Data. The host is my machine, the database name is ArthurLeywin, and the collection name is Capitulos.
I'm receiving the error status=404, error=Not Found, path=/ArthueLeywin/Capitulos/6306e6417d5b2e259951f292}
The code of the Test is
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(SpringExtension::class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TbateMobileReederApplicationTests #Autowired constructor(
private val capituloRepository: CapituloRepository,
private val restTemplate: TestRestTemplate
) {
private val defaultCapituloId : ObjectId = ObjectId("6306e6417d5b2e259951f292")
//ObjectId.get()
#LocalServerPort
protected var port: Int = 27017
private fun getRootUrl(): String? = "http://localhost:$port/ArthueLeywin/Capitulos"
#Test
fun `should return single cap by id`() {
val response = restTemplate.getForObject(
getRootUrl() + "/$defaultCapituloId",
Object::class.java
)
val titleResponse = response.toString()
println(titleResponse)
}
}
My controller, if it's needed is
#RestController
#RequestMapping("/Capitulos")
class CapituloController (
private val capituloRepository : CapituloRepository
){
#GetMapping
fun getAllCapitulos() : ResponseEntity<List<Capitulo>>{
val capitulos = capituloRepository.findAll()
return ResponseEntity.ok(capitulos)
}
#GetMapping("/{id}")
fun getOneCapitulo(#PathVariable("id") id: String): ResponseEntity<Capitulo> {
val capitulo = capituloRepository.findOneById(ObjectId(id))
return ResponseEntity.ok(capitulo)
}
#PostMapping
fun createCapitulo (#RequestBody request: CapituloRequest) : ResponseEntity<Capitulo>{
val capitulo = capituloRepository.save(Capitulo(
nomeCapitulo = request.nomeCapitulo,
urlString = request.urlString,
novelTexto = request.novelTexto
))
return ResponseEntity(capitulo, HttpStatus.CREATED)
}
}
As I said I'm new in this, so please help me with explanations <3

Spring boot - FirebaseMessaging Service injection is not working in another service

I am trying to send firebase push notifications from a spring boot application.
Here I configured the FirebaseMessaging like:
#Configuration
class FirebaseConfig {
var logger: Logger = LoggerFactory.getLogger(FirebaseConfig::class.java)
#Value("\${app.firebase-config-file}")
lateinit var firebaseConfigPath: String
#Bean("firebaseMessaging")
#Throws(IOException::class)
fun firebaseMessaging(): FirebaseMessaging {
val firebaseOptions = FirebaseOptions
.builder()
.setCredentials(GoogleCredentials.fromStream(ClassPathResource(firebaseConfigPath).inputStream))
.build()
val app = FirebaseApp.initializeApp(firebaseOptions, "QInspect")
logger.info("Firebase application has been initialized")
return FirebaseMessaging.getInstance(app)
}
}
Here is the service I am using to send the push notifications:
#Service
class FirebaseNotificationService constructor(
#Autowired val firebaseMessaging: FirebaseMessaging,
#Autowired val mobileDeviceLogService: MobileDeviceLogService
) {
private var logger: Logger = LoggerFactory.getLogger(FirebaseNotificationService::class.java)
private val gson = Gson()
fun sendNotification(token: String, title: String, body: String): Status {
try {
val notification = Notification
.builder()
.setTitle(title)
.setBody(body)
.build()
val message = Message
.builder()
.setToken(token)
.setNotification(notification)
.build()
firebaseMessaging.send(message)
logger.info("Firebase Notification sent)
return Status(Constants.ResponseCode.SUCCESS, "Success")
} catch (e: Exception) {
logger.error("Firebase Notification Failed: ${e.printStackTrace()}")
}
return Status(Constants.ResponseCode.EXCEPTION, "Failed")
}
}
I can able to send the push notification using postman using the endpoint:
#RequestMapping("/notification")
#RestController
class NotificationResource constructor(#Autowired val firebaseService: FirebaseNotificationService) {
#RequestMapping(value = ["/message"], method = [RequestMethod.POST])
#ApiOperation(value = "Send firebase push notification with token", authorizations = [Authorization(value = "basic"), Authorization(value = "oauth2", scopes = [])])
#ResponseBody
#Throws(FirebaseMessagingException::class)
fun sendNotificationMsg(#RequestParam(value = "title", required = true) title: String, #RequestParam(value = "body", required = true) body: String): Status {
val user = SessionUtil.getUser()
return firebaseService.sendNotification(user.token, title, body)
}
Now the problem is when I am trying to inject the FirebaseNotificationService in another service to send the push notifications, But it's not working without any errors.
#Service
#Transactional
class MessageSubscriber {
#Autowired
var firebaseService: FirebaseNotificationService? = null
fun sendPush() {
firebaseService?.sendNotification("token", "Some Title", "Some body")
}
}
What I am missing here?
Update:
I got the issue, it's my mistake it is going inside the FirebaseNotificationService but failing to get the user token which I didn't mention. It was returning here.
val notificationToken =
tokenService.getTopByUserId(user.uuid!!)
?: return Status(Constants.ResponseCode.FAILED, "User or token not found")

Blocking problem of graphql-kotlin with DataLoader&BatchLoader

I used the framework "https://github.com/ExpediaGroup/graphql-kotlin" to learn graphql programming of kotlin under springframework.
I used DataLoader&BatchLoader to resolve the 'N+1' loading problem.
When the scope of DataLoader objects is singleton, it works, but it's not my goal because temporary cache mechanism should not be bridging over different requests.
Then I changed the scope of DataLoader objects to be prototype, in all probability, the graphql query may be blocking and associated objects will not be loaded, client waits the response forever.
What's the reason and how can I resolve it?
I did it like this:
Create a simple springboot application, add maven dependency of graph-kotlin
<dependency>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-spring-server</artifactId>
<version>2.0.0.RC3</version>
</dependency>
Create two model classes(Note: Their code will be changed in the final step)
data class Department(
val id: Long,
val name: String
)
data class Employee(
val id: Long,
val name: String,
#GraphQLIgnore val departmentId: Long
)
Create two mocked repository objects
val DEPARTMENTS = listOf(
Department(1L, "Develop"),
Department(2L, "Test")
)
val EMPLOYEES = listOf(
Employee(1L, "Jim", 1L),
Employee(2L, "Kate", 1L),
Employee(3L, "Tom", 2L),
Employee(4L, "Mary", 2L)
)
#Repository
open class DepartmentRepository {
companion object {
private val LOGGER = LoggerFactory.getLogger(DepartmentRepository::class.java)
}
open fun findByName(namePattern: String?): List<Department> = //For root query
namePattern
?.takeIf { it.isNotEmpty() }
?.let { pattern ->
DEPARTMENTS.filter { it.name.contains(pattern) }
}
?: DEPARTMENTS
open fun findByIds(ids: Collection<Long>): List<Department> { // For assciation
LOGGER.info("BatchLoad departments by ids: [${ids.joinToString(", ")}]")
return DEPARTMENTS.filter { ids.contains(it.id) }
}
}
#Repository
open class EmployeeRepository {
companion object {
private val LOGGER = LoggerFactory.getLogger(EmployeeRepository::class.java)
}
open fun findByName(namePattern: String?): List<Employee> = //For root query
namePattern
?.takeIf { it.isNotEmpty() }
?.let { pattern ->
EMPLOYEES.filter { it.name.contains(pattern) }
}
?: EMPLOYEES
open fun findByDepartmentIds(departmentIds: Collection<Long>): List<Employee> { // For association
LOGGER.info("BatchLoad employees by departmentIds: [${departmentIds.joinToString(", ")}]")
return EMPLOYEES.filter { departmentIds.contains(it.departmentId) }
}
}
Create a graphql query object to export root query operations
#Service
open class OrgService(
private val departmentRepository: DepartmentRepository,
private val employeeRepository: EmployeeRepository
) : Query {
fun departments(namePattern: String?): List<Department> =
departmentRepository.findByName(namePattern)
fun employees(namePattern: String?): List<Employee> =
employeeRepository.findByName(namePattern)
}
Create an abstract class for many-to-one associated object loading
abstract class AbstractReferenceLoader<K, R: Any> (
batchLoader: (Collection<K>) -> Collection<R>,
keyGetter: (R) ->K,
optionsInInitializer: (DataLoaderOptions.() -> Unit) ? = null
): DataLoader<K, R?>(
{ keys ->
CompletableFuture.supplyAsync {
batchLoader(keys)
.associateBy(keyGetter)
.let { map ->
keys.map { map[it] }
}
}
},
optionsInInitializer?.let {
DataLoaderOptions().apply {
this.it()
}
}
)
Create an abstract class for one-to-many associated collection loading
abstract class AbstractListLoader<K, E>(
batchLoader: (Collection<K>) -> Collection<E>,
keyGetter: (E) ->K,
optionsInInitializer: (DataLoaderOptions.() -> Unit) ? = null
): DataLoader<K, List<E>>(
{ keys ->
CompletableFuture.supplyAsync {
batchLoader(keys)
.groupBy(keyGetter)
.let { map ->
keys.map { map[it] ?: emptyList() }
}
}
},
optionsInInitializer?.let {
DataLoaderOptions().apply {
this.it()
}
}
)
Create annotation to let spring manager DataLoader beans by prototype scope
#Retention(RetentionPolicy.RUNTIME)
#Target(AnnotationTarget.CLASS)
#Component
#Scope(
ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.NO
)
annotation class DataLoaderComponent
Create loader bean to load the parent object reference of Employee object
#DataLoaderComponent
open class DepartmentLoader(
private val departmentRepository: DepartmentRepository
): AbstractReferenceLoader<Long, Department>(
{ departmentRepository.findByIds(it) },
{ it.id },
{ setMaxBatchSize(256) }
)
Create loader bean to load the child object collection of Department object
#DataLoaderComponent
open class EmployeeListByDepartmentIdLoader(
private val employeeRepository: EmployeeRepository
): AbstractListLoader<Long, Employee>(
{ employeeRepository.findByDepartmentIds(it) },
{ it.departmentId },
{ setMaxBatchSize(16) }
)
Create an GraphQL configuration to let 'graphql-kotlin' know all the DataLoader beans
#Configuration
internal abstract class GraphQLConfig {
#Bean
open fun dataLoaderRegistryFactory(): DataLoaderRegistryFactory =
object: DataLoaderRegistryFactory {
override fun generate(): DataLoaderRegistry = dataLoaderRegistry()
}
#Bean
#Scope(
ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.NO
)
protected open fun dataLoaderRegistry(
loaders: List<DataLoader<*, *>>
): DataLoaderRegistry =
DataLoaderRegistry().apply {
loaders.forEach { loader ->
register(
loader::class.qualifiedName,
loader
)
}
}
#Lookup
protected abstract fun dataLoaderRegistry(): DataLoaderRegistry
}
Add tow methods into DataFetchingEnvironment to get DataLoader objects
inline fun <reified L: AbstractReferenceLoader<K, R>, K, R> DataFetchingEnvironment.getReferenceLoader(
): DataLoader<K, R?> =
this.getDataLoader<K, R?>(L::class.qualifiedName)
inline fun <reified L: AbstractListLoader<K, E>, K, E> DataFetchingEnvironment.getListLoader(
): DataLoader<K, List<E>> =
this.getDataLoader<K, List<E>>(L::class.qualifiedName)
Change the code of Department and Employee, let them support association
data class Department(
val id: Long,
val name: String
) {
suspend fun employees(env: DataFetchingEnvironment): List<Employee> =
env
.getListLoader<EmployeeListByDepartmentIdLoader, Long, Employee>()
.load(id)
.await()
}
data class Employee(
val id: Long,
val name: String,
#GraphQLIgnore val departmentId: Long
) {
suspend fun department(env: DataFetchingEnvironment): Department? =
env
.getReferenceLoader<DepartmentLoader, Long, Department>()
.load(departmentId)
.await()
}
Build & Run
Start the SpringBoot applications, open http://locathost:8080/playground.
Execute the query, the result may be success or failed!
{
employees {
id
name
department {
id
name
employees {
id
name
}
}
}
}
If it is success, the client can get the response, and the server log is
2020-03-15 22:47:26.366 INFO 35616 --- [onPool-worker-5] org.frchen.dal.DepartmentRepository : BatchLoad departments by ids: [1, 2]
2020-03-15 22:47:26.367 INFO 35616 --- [onPool-worker-5] org.frchen.dal.EmployeeRepository : BatchLoad employees by departmentIds: [1, 2]
If it is failed, the client is blocking and waits for the response forever, and the server log is
2020-03-15 22:53:43.159 INFO 35616 --- [onPool-worker-6] org.frchen.dal.DepartmentRepository : BatchLoad departments by ids: [1, 2]

Resources