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")
Related
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
im trying to set jackson serializer at spring MessageTemplate to send messages alredy formated using jacksonMapper and SQS/SNS, the problem is that it keeps sending messages unformated, even with notation #JsonProperty or #JsonFormat
here is my configs:
#Configuration
class AwsMessagingConfigurationLocal(
#Value("\${cloud.aws.region.static}") val region: String,
#Value("\${cloud.aws.credentials.accessKey}") val accessKey: String,
#Value("\${cloud.aws.credentials.secretKey}") val secretKey: String,
#Value("\${cloud.aws.endpoint}") val endpoint: String
) {
#Bean
fun notificationMessageTemplate(): NotificationMessagingTemplate {
return NotificationMessagingTemplate(buildAmazonSNSAsync())
}
#Bean
fun queueMessagingTemplate(): QueueMessagingTemplate {
return QueueMessagingTemplate(buildAmazonSQSAsync())
}
#Bean
fun simpleMessageListenerContainerFactory(): SimpleMessageListenerContainerFactory {
val messageListenerContainerFactory = SimpleMessageListenerContainerFactory()
messageListenerContainerFactory.setAmazonSqs(buildAmazonSQSAsync())
return messageListenerContainerFactory
}
#Bean
fun queueMessageHandlerFactory(objectMapper: ObjectMapper): QueueMessageHandlerFactory {
val messageConverter = MappingJackson2MessageConverter()
messageConverter.objectMapper = objectMapper.registerModule(JavaTimeModule())
val queueMessageHandlerFactory = QueueMessageHandlerFactory()
queueMessageHandlerFactory.messageConverters = listOf(messageConverter)
queueMessageHandlerFactory.setArgumentResolvers(listOf(NotificationMessageArgumentResolver(messageConverter)))
return queueMessageHandlerFactory
}
private fun buildAmazonSNSAsync(): AmazonSNSAsync {
return AmazonSNSAsyncClientBuilder.standard()
.withEndpointConfiguration(AwsClientBuilder.EndpointConfiguration(endpoint, region))
.withCredentials(AWSStaticCredentialsProvider(BasicAWSCredentials(accessKey, secretKey)))
.build()
}
private fun buildAmazonSQSAsync(): AmazonSQSAsync {
return AmazonSQSAsyncClientBuilder.standard()
.withEndpointConfiguration(AwsClientBuilder.EndpointConfiguration(endpoint, region))
.withCredentials(AWSStaticCredentialsProvider(BasicAWSCredentials(accessKey, secretKey)))
.build()
}
}
but when i send an object with LocalDateTime, even with #JsonFormat it keeps sending wrong format:
data class Blob(
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
val timestamp: LocalDateTime
)
i made this simple dispatch function to send messages through SNS(topic) and connected a queue to receive the responses.
class Dispatcher(
private val private val notificationTemplate: NotificationMessagingTemplate
)
fun dispatch(blob: Blob) {
val header = mapOf("randon" to "message")
notificationTemplate.convertAndSend("topic", blob, header)
}
wanted result :
\"timestamp\"=\"2021-11-26T19:18:46.905505\"
current result :
"timestamp\":{\"nano\":913070000,\"year\":2021,\"monthValue\":11,\"dayOfMonth\":26,\"hour\":19,\"minute\":18,\"second\":46,\"month\":\"NOVEMBER\",\"dayOfWeek\":\"FRIDAY\",\"dayOfYear\":330,\"chronology\":{\"calendarType\":\"iso8601\",\"id\":\"ISO\"}}
i don't have any clue about what is happening.
PS. i tested SQS too, and the result is the same.
after searching inside awspring, i found that the SimpleMessageConverter is set by default, after some research, i found the right way to set the custom mapper at sendAndConvert() function. here is the configurtions:
#Bean
fun notificationMessageTemplate(objectMapper: ObjectMapper): NotificationMessagingTemplate {
// here we set the custom message converter
val messageConverter = MappingJackson2MessageConverter()
messageConverter.objectMapper = objectMapper
// as we want to send it as a Json, we need to set the serialization to String
messageConverter.serializedPayloadClass = String::class.java
val notificationMessageTemplate = NotificationMessagingTemplate(buildAmazonSNSAsync())
// here is where we set the proper message converter
notificationMessageTemplate.messageConverter = messageConverter
return notificationMessageTemplate
}
#Bean
fun queueMessagingTemplate(objectMapper: ObjectMapper): QueueMessagingTemplate {
val messageConverter = MappingJackson2MessageConverter()
messageConverter.objectMapper = objectMapper
messageConverter.serializedPayloadClass = String::class.java
val queueMessageConverter = QueueMessagingTemplate(buildAmazonSQSAsync())
queueMessageConverter.messageConverter = messageConverter
return queueMessageConverter
}
after setting this mappers, every notation will be working fine.
here is the result:
\"timestamp\":\"2021-11-29T17:46:52Z\"
I'm trying to validate text parameter to be not blank in spring boot kotlin project, but its not working correctly and returning just response code 200 instead of 422.
org.springframework.boot:spring-boot-starter-validation" is also present in classpath.
Controller.kt
#Validated
#RestController
#RequestMapping(ApiEndPoints.Search.SEARCH_ROOT_PATH)
class SearchController(
private val searchService: SearchService
) {
#GetMapping(ApiEndPoints.Search.BY_USERS)
fun searchUsers(
#Valid #NotBlank(message = "Text must not be blank") #RequestParam text: String,
#RequestParam(defaultValue = Constants.DEFAULT_PAGE_0) page: Int,
#RequestParam(defaultValue = Constants.DEFAULT_SIZE_15) size: Int
): ResponseEntity<ApiResponse.Success<Map<String, Any>>> {
val users = searchService.searchUsers(text, page, size)
return ResponseEntity.ok(ApiResponse.Success(true, users.createResponseMapFromSlice(Constants.USERS_LABEL)))
}
}
ControllerAdvice.kt
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
class GlobalExceptionHandler : ResponseEntityExceptionHandler() {
override fun handleMethodArgumentNotValid(
ex: MethodArgumentNotValidException,
headers: HttpHeaders,
status: HttpStatus,
request: WebRequest
): ResponseEntity<Any> {
val errors = hashMapOf<String, String>()
ex.bindingResult.allErrors.forEach {
val fieldName = (it as FieldError).field
val errorMessage = it.defaultMessage
errors[fieldName] = errorMessage.toString()
}
return ResponseEntity(
ApiResponse.ValidationError(
errors,
ErrorCodes.VALIDATION_FAILED
), HttpStatus.UNPROCESSABLE_ENTITY
)
}
}
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()
}
I'm attempting to validate a String #PathVariable inside a Kotlin Spring controller.
#RestController
#Validated
#RequestMapping("/kotlin/")
open class KStringController {
#GetMapping(path = ["string/{username}"],
produces = [APPLICATION_JSON_VALUE])
#ResponseBody
fun validateStringPathVariable(
#Pattern(regexp = "[A-Za-z]+", message = "Username Pattern Validation Message")
#Size(min = 2, max = 15, message = "Username Size Validation Message")
#PathVariable("username") username: String?
): ResponseEntity<String>? {
logger.info { String.format("validateStringPathVariable: Got Username [%s]", username) }
System.out.printf("validateStringPathVariable: Username is [%s]%n", username)
return ResponseEntity.ok("Username is valid")
}
}
I have unit tests for it:
#ExtendWith(SpringExtension::class)
#WebMvcTest(KStringController::class)
#AutoConfigureMockMvc
class KStringGetControllerTest {
#field:Autowired
private lateinit var mvc: MockMvc
#Test
#Throws(Exception::class)
fun validStringPathVariable() {
val request: MockHttpServletRequestBuilder = givenARequestFor("/kotlin/string/mike")
val actions: ResultActions = whenTheRequestIsMade(request)
thenExpect(actions,
MockMvcResultMatchers.status().isOk,
MockMvcResultMatchers.content().bytes("Username is valid".toByteArray()))
}
#Test
#Throws(Exception::class)
fun shoutsWhenStringPathVariableIsTooShort() {
val request = givenARequestFor("/kotlin/string/a")
val actions = whenTheRequestIsMade(request)
val response = """{
"validationErrors": [
{
"fieldName": "validateStringPathVariable.username",
"message": "Username Size Validation Message"
}
]
}"""
val content = MockMvcResultMatchers.content()
thenExpect(actions,
MockMvcResultMatchers.status().isBadRequest,
content.contentType(MediaType.APPLICATION_JSON),
content.json(response))
}
private fun givenARequestFor(url: String): MockHttpServletRequestBuilder {
return MockMvcRequestBuilders.get(url)
.characterEncoding("UTF-8")
}
#Throws(Exception::class)
private fun whenTheRequestIsMade(request: MockHttpServletRequestBuilder): ResultActions {
return mvc.perform(request)
}
#Throws(Exception::class)
private fun thenExpect(resultActions: ResultActions, vararg matchers: ResultMatcher) {
resultActions.andExpect(ResultMatcher.matchAll(*matchers))
}
Currently the second test is failing; it's returning a 200 response code when I'm expecting a 400. Much much more code at https://github.com/Mavelous/spring-validation.
With help from others, I've managed to find a solution.
Quick answer: Change the function to open fun validateStringPathVariable, and the validations will start working.
Alternative answer: Use the org.jetbrains.kotlin.plugin.spring plugin in your build.gradle file.
I also created a blog post with a longer description: http://mavelo.us/2021/04/07/spring-validation-in-kotlin.html