AWS Spring can't set object mapper - spring

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\"

Related

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 circular reference with multi-tenant db setup

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))
}
}

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")

Using ConnectableFlux for hot stream REST endpoint

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()
}

Spring Data Redis SET command supports EX and NX

Do Spring Data Redis support SET command with Options
My use case:
127.0.0.1:6379> set lock.foo RUNNING NX EX 20
Then check if Redis return value OK or (nil)
Use RedisTemplate#execute(RedisCallback<T> method, demo:
#Autowired
private RedisTemplate redisTemplate;
public void test() {
String redisKey = "lock.foo";
String value = "RUNNING";
long expire = 20L;
Boolean result = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
byte[] redisKeyBytes = redisTemplate.getKeySerializer().serialize(redisKey);
byte[] valueBytes = redisTemplate.getValueSerializer().serialize(value);
Expiration expiration = Expiration.from(expire, TimeUnit.SECONDS);
return connection.set(redisKeyBytes, valueBytes, expiration, RedisStringCommands.SetOption.SET_IF_ABSENT);
});
System.out.println("result = " + result);
}
RedisTemplate config:
#Configuration
public class RedisConfig {
#Bean
public RedisSerializer<String> keySerializer() {
return new StringRedisSerializer();
}
#Bean
public RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
#Bean
public RedisTemplate redisTemplate(RedisTemplate redisTemplate, RedisSerializer keySerializer, RedisSerializer valueSerializer) {
//set key serializer
redisTemplate.setKeySerializer(keySerializer);
redisTemplate.setHashKeySerializer(keySerializer);
//set value serializer
redisTemplate.setValueSerializer(valueSerializer);
redisTemplate.setHashValueSerializer(valueSerializer);
return redisTemplate;
}
}
Cannot see any Spring template value operations solutions, so I did a 'native' execute on the connection org.springframework.data.redis.connection.StringRedisConnection#execute(java.lang.String, java.lang.String...)
Then it is up to me to take care of processing of arguments and the result.

Resources