How can I easily cache Kotlin Objects in Redis using json via Jackson? - spring-boot

I have a Spring boot app written in Kotlin where I would like to enable caching in Redis. I'd like to have the objects stored as serialized JSON and ideally don't want to have to register each type that could be potentially cached. I have some configuration that mostly works, with a big caveat.
#Bean
fun redisCacheConfiguration(): RedisCacheConfiguration {
val objectMapper =
ObjectMapper()
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
val serializer = GenericJackson2JsonRedisSerializer(objectMapper)
return RedisCacheConfiguration
.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
}
I'm having a little trouble understanding the different values for DefaultTyping but NON_FINAL seems to be the most expansive. However, since objects in Kotlin are final by default, this only works for objects flagged as "open". Ideally I'd like to avoid having to "open" objects just so they can be cached.
Is there some other way I can make this work?

I had the same problem. You should use "open" classes. But this will not help you with data classes, because you cannot make them "open".
There is a plugin called "all-open" where you can define annotations. If you use these annotations classes become "open", even data classes.
spring-kotlin plugin uses "all-open" plugin under the hood, so spring annotations like #Service, #Component etc. make classes open for AOP, because proxying requires you to inherit from classes.
If you use spring-kotlin plugin, there is nice annotation that makes sense for you problem, it is used in Spring Cache, its name is #Cacheable.
If you use #Cacheable on your classes, they will become open and save their type-info to json (ex: {#class: "com.example.MyClass", ...}) when you include this code:
val objectMapper =
ObjectMapper()
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
val serializer = GenericJackson2JsonRedisSerializer(objectMapper)
More details: https://kotlinlang.org/docs/reference/compiler-plugins.html
Shortly: You don't have to do anything except adding #Cacheable annotation to the classes you want, and it fits by sense also IMO.

The issues have been solved. Therefore we can remove #Cacheble hack from the code. You have to modify your ObjectMapper with the next implementation
val om = ObjectMapper()
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
.activateDefaultTyping(BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(Any::class.java)
.build(), ObjectMapper.DefaultTyping.EVERYTHING)
val serializer = GenericJackson2JsonRedisSerializer(om)
Fixed Maven Jackon dependency
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0.pr2</version>
</dependency>

You can look this:
https://github.com/endink/caching-kotlin
Its support both jackson and kryo

I had a problem since my data classes were extending some interfaces, so generic would not do the trick, I end up with this solution, its a custom serialiser and deserialiser, the generic would just save time compiled getter as a variable and break the deserialise
#Configuration
#EnableCaching
class CachingConfiguration() : CachingConfigurerSupport() {
#Bean
fun configureRedisAction(): ConfigureRedisAction? {
return ConfigureRedisAction.NO_OP
}
#Autowired
private lateinit var redisConnectionFactory: RedisConnectionFactory
companion object {
const val CACHE_KEY = "cache-key"
}
#Bean
override fun cacheManager(): CacheManager? {
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.withCacheConfiguration(CACHE_KEY, cacheConfig<User>(ofMinutes(5)))
.build()
}
private inline fun <reified T> cacheConfig(ttl: Duration): RedisCacheConfiguration {
return RedisCacheConfiguration
.defaultCacheConfig()
.serializeValuesWith(fromSerializer(object : RedisSerializer<Any> {
val mapper = ObjectMapper().registerModule(ParameterNamesModule())
override fun serialize(t: Any?): ByteArray? {
return mapper.writeValueAsBytes(t)
}
override fun deserialize(bytes: ByteArray?): Any? {
return try {
mapper.readValue(bytes!!, T::class.java) as Any
} catch (e: Exception) {
null
}
}
})
)
.entryTtl(ttl)
}
}

Related

Kotlin - Mapstruct - #AfterMapping can only be applied to an implemented class

I am currently working on a mapstruct mapping on Kotlin, which has some relationship that uses Spring boot repository and services to get the object for further processing. However, I was not able to implement #AfterMapping.
#Mapper(componentModel = "spring")
interface Objectmapper {
#Mappings(
Mapping(source = "aCode", target = "a.code"),
Mapping(source = "bCode", target = "b.code")
)
fun convertFormDtoToEntity(
dto: ObjectFormDto,
#Context aRepo: ARepository,
#Context bService: BService
): Object
#AfterMapping
fun afterMappingFormDtoToEntity(
dto: ObjectFormDto,
#Context aRepo: ARepository,
#Context bService: BService,
#MappingTarget object: Object
){
object.a = aRepo.findByA(object.a.code)
object.b = bService.getB(object.b.code)
}
}
My goal is to implement the afterMappingFormDtoToEntity() after the mapping on convertFormToEntity() is done, but I was not able to finish the kaptKotlin job and returned the error
error: #AfterMapping can only be applied to an implemented class
public abstract void afterMappingFormDtoToEntity(#org.jetbrains.annotations.NotNull()...
My current mapstruct version is "1.5.2.Final", with kapt version "1.6.10", the following kapt settings in build.gradle.kts
kapt {
arguments {
arg("mapstruct.unmappedTargetPolicy", "IGNORE")
}
keepJavacAnnotationProcessors = true
}
Implemented methods in Kotlin interfaces are not marked as implemented. Therefore MapStruct thinks that the method is abstract and needs to be implemented.
In order for things to work you'll need to annotate the method with #JvmDefault
It was solve by changing the mapper type
from interface Objectmapper to abstract class ObjectMapper and convert all the methods to abstract except the #AfterMapping method
The detail is written in my Medium blog post:
https://medium.com/me/stats/post/5c3f468ee261

Spring and serialization to Json - how to customize globally Jackson without Spring Boot

I'm using clean Spring MVC framework (v5.3.21) without Spring Boot.
I was working with Gson library, which was used by Spring to serialize view models, returned with request methods.
public class Coffee {
String name = "n";
String brand = "b";
}
#RequestMapping(value={"/coffe"}, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Coffee getCoffee() {
return new Coffee();
}
Recently I added Jackson (v 2.13.3) on the classpath and I've noticed serialization works much different. First of all - in Gson non-private field where serialized by default, now they are not visible at the client side.
I know I can add annotation
#JsonAutoDetect(fieldVisibility = Visibility.NON_PRIVATE)
to all the model classes, or change fields to public (Jackson default visibility for fields is PUBLIC, as far as I found out).
But I would like to change just once, globally, in configuration, without rewriting code of many
I tried many options, but none of them doesn't work without Spring Boot.
Do you know to change this default setting with clean Spring?
You can create ObjectMapper bean which can be global for application
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.NON_PRIVATE));
return mapper;
}

How to avoid Spring WebFlux filter from being called twice?

After a long exhaustive search, asking for help on this. I'm writing a JwtTokenFilter in Spring WebFlux coupled with Spring Security. The problem is this filter is being called twice on a single request. The filter code is below.
#Component
class JwtTokenAuthenticationFilter(
#Autowired val tokenProvider: JwtTokenProvider
) : WebFilter {
private val HEADER_PREFIX = "Bearer "
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void?> {
val token = resolveToken(exchange.request)
return if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
val authentication: Authentication = tokenProvider.getAuthentication(token)
chain.filter(exchange)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
} else
chain.filter(exchange)
}
private fun resolveToken(request: ServerHttpRequest): String? {
val bearerToken = request.headers.getFirst(HttpHeaders.AUTHORIZATION)
return if (StringUtils.hasText(bearerToken) && bearerToken!!.startsWith(HEADER_PREFIX)) {
bearerToken.substring(7)
} else null
}
}
This problem can be avoided by removing #Component annotation on the filter but that would mean I'll have to create this object and all its dependencies in the path, which totally takes away the advantage of dependency inversion of Spring. Therefore, I want to retain using the Spring annotations to get the work done. However, this creates the problem that the filter is registered at two places i.e.., at servlet container and Spring Security.
This solution presented in this article talks about it.
But couldn't find a way to apply the solution on that article to WebFilter as the solution in the article is for servlet Filters.
So, is there a way to avoid this bean from being invoked by servlet container and allow it to be invoked by Spring Security only. Or something else is the issue in the code that you see?

Spring multiple entity JSON serializers

I am looking for a way to create multiple json serializers for my entity. I have created service layer, custom serilizers and now I have problem with implementation of this things.
My Service class looks like:
#Service
class TeamsService(#Autowired private val teamsRepository: TeamsRepository) : ITeamsService{
override fun findAll(): String? {
var objectMapper = ObjectMapper()
var simpleModule = SimpleModule()
simpleModule.addSerializer(Teams::class.java, TeamsSerializer())
objectMapper.registerModule(simpleModule)
return objectMapper.writeValueAsString(teamsRepository.findAll())
}
}
And my Controller looks like:
#RestController
#RequestMapping("/v1")
class MainController(#Autowired private val teamsService: TeamsService) {
#GetMapping("/teams")
fun teams(): String? = teamsService.findAll()
}
Now I have problem that my response lost all headers and appears as text/plain not text/json, like it was before I added custom mapper.
I Was reading about projections but I am not sure if I should use them. I do not want to have query parameters in my url.
I found solution. The best way to do this is to use ModelMapper library. You can simply map entity to custom classes and serialize

Preventing Spring Boot from creating nested map from dot-separated key in application.yml?

I have a problem with Spring Boot creating a nested map from a dot-separated key. It's essentially the same problem that is described here, but the solution suggested there doesn't work for me. I'm using Spring Boot 1.5.3.RELEASE in case it matters. My applications.yml file contains this:
props:
webdriver.chrome.driver: chromedriver
My config class:
#Configuration
#EnableConfigurationProperties
public class SpringConfig {
private Map<String, String> props = new HashMap<>();
#ConfigurationProperties(prefix = "props")
public void setProps(Map<String, String> props) {
this.props = props;
}
#ConfigurationProperties(prefix = "props")
#Bean(destroyMethod="", name = "props")
public Map<String, String> getProps() {
return props;
}
}
Unfortunately, after Spring Boot processes the YAML file, the dot separated key gets split up into sub-maps. The result from callig getProps() and printing the result to System.out looks like this:
{webdriver={chrome={driver=chromedriver}}}
I've tried changing the type of the props field to Properties, Map<String, Object> etc, but nothing seems to make any difference.
I haven't found any way of manipulating the parsing behavior to accomplish what I want. Any help is much appreciated. I've spent so much time on this, that I'll go blind if I look at the code any further.
Try using YamlMapFactoryBean this will load YAML as MAP.
#Bean
public YamlMapFactoryBean yamlFactory() {
YamlMapFactoryBean factory = new YamlMapFactoryBean();
factory.setResources(resource());
return factory;
}
public Resource resource() {
return new ClassPathResource("application.yml");
}
public Map<String, String> getProps() {
props = yamlFactory().getObject();
return props;
}
The output looks
props{webdriver.chrome.driver=chromedriver}
After much experimenting, this seemed to work:
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
#ConfigurationProperties
public class SpringConfig {
private Properties info = new Properties();
public Properties getProps() {
return info;
}
}
}
But I had to put single quotes around the YAML entry, otherwise Spring Boot would make the property nested:
props:
'webdriver.chrome.driver': chromedriver
'foo.bar.baz': foobarbaz
A couple of things I noticed. The getter for the Properties (getProps() in this case) must be declared public, and it has to match the property key that you're trying to bind in the YAML. I.e. since the key is 'props', the getter has to be called getProps(). I guess it's logical, and documented somewhere, but that had slipped me by somehow. I thought by using the prefix="foobar" on the #ConfigurationProperties annotation, that wasn't the case, but it seems to be. I guess I should RTFM ;-)

Resources