Redis Template won't serialize key and value - kotlin

I want to use RedisTemplate on spring boot. This is my configuration file:
#Configuration
#EnableRedisRepositories
class RedisConfig {
#Value("\${spring.redis.host}")
lateinit var redisHost: String
#Value("\${spring.redis.port}")
lateinit var redisPort: String
#Bean
fun redisConnectionFactory(): JedisConnectionFactory? {
val redisStandaloneConfiguration = RedisStandaloneConfiguration()
redisStandaloneConfiguration.hostName = redisHost
redisStandaloneConfiguration.port = redisPort.toInt()
return JedisConnectionFactory(redisStandaloneConfiguration)
}
#Bean
fun redisTemplate(): RedisTemplate<Int, String> {
val redisTemplate = RedisTemplate<Int, String>()
val stringSerializer: RedisSerializer<String> = StringRedisSerializer()
redisTemplate.setConnectionFactory(redisConnectionFactory()!!)
redisTemplate.keySerializer = stringSerializer
redisTemplate.valueSerializer = stringSerializer
redisTemplate.afterPropertiesSet()
return redisTemplate
}
}
In my class i've got something like this:
#Service
class UserService {
#Autowired
var redisTemplate = RedisTemplate<Int, String>()
val user = User("1", "Mike", "Tobey", "Male", "MT#gmail.com")
val user1 = User("1", "Mike", "Tobey", "Male", "MT#gmail.com")
fun foo(user: User, user1: User) {
if (user.lastName == user1.lastName && user.firstName == user1.firstName) {
redisTemplate.opsForValue().set(user.hashCode(), user.id)
} else if (user.gender == user1.gender && user.firstName == user1.firstName) {
redisTemplate.opsForValue().set(user1.hashCode(), user1.id)
}
}
The User.class is marked as #Serializable and #RedisHash. In the redisTemplate.opsForValue().set() method, it won't recognize the key and the value at all. This is what the program returns me:
Cannot invoke "org.springframework.data.redis.serializer.RedisSerializer.serialize(Object)" because the return value of "org.springframework.data.redis.core.AbstractOperations.valueSerializer()" is null.
Can anybody help?
I tried to set key and values in value store for redisTemplate but it seems like code won't serialize those values.

Related

ClassCastException when using Mockito.thenAnswer

I am new to Kotlin. I get exception when trying to use Mockito's thenAnswer method
Controller:
#RestController
class SampleRestController(
val sampleService: SampleService
) {
#PostMapping(value = ["/sample-endpoint"], consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
fun sampleEndpoint(#RequestBody values: List<String>): ResponseEntity<String> {
val response = sampleService.serviceCall(values)
return ResponseEntity.status(HttpStatus.OK).body(response)
}
}
Service:
#Service
#Transactional
class SampleService {
fun serviceCall(values: List<String>): String {
return values.joinToString("")
}
}
Test:
#ExtendWith(MockitoExtension::class)
internal class SampleRestControllerTest {
#Mock
private lateinit var sampleService: SampleService
private lateinit var mockMvc: MockMvc
private lateinit var objectMapper: ObjectMapper
private lateinit var sampleRestController: SampleRestController
#BeforeEach
fun before() {
MockitoAnnotations.initMocks(this)
objectMapper = ObjectMapper()
.registerModule(JavaTimeModule())
.registerKotlinModule()
sampleRestController = SampleRestController(sampleService)
mockMvc = MockMvcBuilders.standaloneSetup(sampleRestController).build()
}
#Test
fun doTest() {
val testData = listOf("123", "456")
//Mockito.`when`(sampleService.serviceCall(testData)).thenReturn("123456")
//Mockito.`when`(sampleService.serviceCall(testData)).thenAnswer { invocation -> "123456" }
Mockito.`when`(sampleService.serviceCall(testData)).thenAnswer { invocation -> {
val numbers = invocation.getArgument<List<String>>(0)
if ("123" == numbers[0] && "456" == numbers[1]) {
"123456"
} else {
"654321"
}
} }
val result: MvcResult = mockMvc.perform(
MockMvcRequestBuilders.post("/sample-endpoint")
.content(objectMapper.writeValueAsString(testData))
.contentType(MediaType.APPLICATION_JSON_VALUE)
)
.andExpect(status().isOk)
.andReturn()
assertEquals("123456", result.response.contentAsString)
}
}
The unit test is working fine when using the thenReturn() and also when using thenAnswer() without any if condition.
When I try to use thenAnswer with if condition then I get classCastException.
Probably because Kotlin only accepts non-null value? How do I resolve this issue.
Check this.
#Test
fun doTest() {
val testData = listOf("123", "456")
//Mockito.`when`(sampleService.serviceCall(testData)).thenReturn("123456")
//Mockito.`when`(sampleService.serviceCall(testData)).thenAnswer { invocation -> "123456" }
Mockito.`when`(sampleService.serviceCall(testData)).thenAnswer(Answer<Any?> { invocationOnMock: InvocationOnMock ->
val values = invocationOnMock.getArgument<List<String>>(0)
if ("123" == values[0] && "456" == values[1]) {
return#Answer "123456"
}
return#Answer "654321"
})
val result: MvcResult = mockMvc.perform(
MockMvcRequestBuilders.post("/sample-endpoint")
.content(objectMapper.writeValueAsString(testData))
.contentType(MediaType.APPLICATION_JSON_VALUE)
)
.andExpect(status().isOk)
.andReturn()
assertEquals("123456", result.response.contentAsString)
}

Spring webtestclient serializes dates to timestamps instead of dates

I am trying to check if the data I get back from the webtestclient is the same as what I expect. But the ZonedDateTime from the User data class is not just shown as a date but as a timestamp while I have applied Jackson to the webtestclient codecs. Example: 2021-12-09T16:39:43.225207700+01:00 is converted to 1639064383.225207700 while I expect nothing to change. Could someone maybe explain what I am doing wrong. (Using this jackson config when calling this endpoint outside of the test gives the date not as timestamp)
WebTestClientUtil:
object WebTestClientUtil {
fun webTestClient(routerFunction: RouterFunction<ServerResponse>): WebTestClient {
return WebTestClient
.bindToRouterFunction(routerFunction)
.configureClient()
.codecs { configurer: ClientCodecConfigurer ->
configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON))
configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON))
}
.build()
}
}
Testcase:
#Test
fun `get user when given correct data`() {
val user = GlobalMocks.mockedUser
coEvery { userRepository.getUserWithData(any()) } returns user
val result = webTestClient.get()
.uri("/api/v1/user/${user.userId}")
.exchange()
.expectStatus().is2xxSuccessful
.expectBody<Result>().returnResult().responseBody?.payload
assertEquals(user, result)
}
data class Result(
val payload: User
)
Jackson config:
class JacksonConfig {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
val objectMapper = jacksonObjectMapper().applyDefaultSettings()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}
EDIT: Also found this issue which makes me think that it might have something to do with bindToRouterFunction.
You need to define an ObjectMapper bean so that the auto-configured one is not used:
#Configuration(proxyBeanMethods = false)
class JacksonConfiguration {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
}
#Bean
fun objectMapper() = jacksonObjectMapper().applyDefaultSettings ()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}

Testing Soap Consumer. 'uri' must not be empty

I have a method for calling the "marshalSendAndReceive" method on sending a message over Soap.
class SoapConnector : WebServiceGatewaySupport() {
fun getClient(documentId: String): Person {
val request = getSearchRequest(documentId)
val response = webServiceTemplate.marshalSendAndReceive(request) as SearchResponse
return getPerson(response)
}
I also have a configuration file.
#Configuration
class SearchClientConfig {
#Bean
fun marshaller(): Jaxb2Marshaller? {
return Jaxb2Marshaller().apply {
contextPath = "wsdl" //here path to generated java classes from wsdl
}
}
#Bean
fun searchCilent(marshallerJaxb: Jaxb2Marshaller): SoapConnector {
return SoapConnector().apply {
defaultUri = "http://20.40.59.1:8080/cdi/soap/services/ServiceWS"
marshaller = marshallerJaxb
unmarshaller = marshallerJaxb
}
}
}
I want to Mock this method.
#RunWith(SpringRunner::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Sql(scripts = ["classpath:db/init.sql"])
#AutoConfigureEmbeddedDatabase(
beanName = "dataSource",
provider = AutoConfigureEmbeddedDatabase.DatabaseProvider.DOCKER
)
class SoapTest : WebServiceGatewaySupport(){
private lateinit var webServiceGatewaySupport: WebServiceGatewaySupport
private lateinit var connector: SoapConnector
#BeforeEach
fun setUp() {
webServiceGatewaySupport = Mockito.mock(WebServiceGatewaySupport::class.java)
}
#Test
fun getClientTest() {
Mockito.`when`(webServiceTemplate.marshalSendAndReceive(ArgumentMatchers.anyObject())).thenReturn(SearchResponse())
connector.getClient(RandomStringUtils.randomAlphabetic(7))
}
}
But there is a bug:
java.lang.IllegalArgumentException: 'uri' must not be empty
I don't understand what the problem is and I don't know how to make a working Mock of this method.

How to override HashMaps entrySet method in Kotlin?

I want to convert java class from this post Retrofit — Multiple query parameters of same name where name is set dynamically to kotlin.
public class ProxyRetrofitQueryMap extends HashMap<String, Object> {
public ProxyRetrofitQueryMap(Map<String, Object> m) {
super(m);
}
#Override
public Set<Entry<String, Object>> entrySet() {
Set<Entry<String, Object>> newSet = new HashSet<>();
return newSet;
}
}
Conversion does not help, it gives Platform declaration clash. I want to manually create Kotlin class which extends from HashMap, but this class does not have entrySet() function. What to do?
When using a kotlin.collections.HashMap or java.util.HashMap, the entrySet() function is accessible through their entries property, which you can override like so:
class ProxyRetrofitQueryMap : HashMap<String, Any>() {
override val entries: MutableSet<MutableMap.MutableEntry<String, Any>>
get() {
val newSet = HashSet<MutableMap.MutableEntry<String, Any>>()
return newSet
}
}
You can confirm that this property maps to the original function by using it in code and then navigating to its declaration in the IDE. Unfortunately, the documentation doesn't seem to state this behaviour explicitly, or at least I couldn't find it.
Here's the full solution, converted to Kotlin:
class ProxyRetrofitQueryMap(m: MutableMap<String, Any>) : HashMap<String, Any>(m) {
override val entries: MutableSet<MutableMap.MutableEntry<String, Any>>
get() {
val originSet: Set<Map.Entry<String?, Any?>> = super.entries
val newSet: MutableSet<MutableMap.MutableEntry<String, Any>> = HashSet()
for ((key, entryValue) in originSet) {
val entryKey = key ?: throw IllegalArgumentException("Query map contained null key.")
// Skip null values
requireNotNull(entryValue) { "Query map contained null value for key '$entryKey'." }
if (entryValue is List<*>) {
for (arrayValue in entryValue) {
if (arrayValue != null) { // Skip null values
val newEntry: MutableMap.MutableEntry<String, Any> =
SimpleEntry(entryKey, arrayValue)
newSet.add(newEntry)
}
}
} else {
val newEntry: MutableMap.MutableEntry<String, Any> = SimpleEntry(entryKey, entryValue)
newSet.add(newEntry)
}
}
return newSet
}
}

Constructor visibility restricted to file

I want to create an easier way to handle SharedPreferences.
The way I want to call it is like this
get preference:
val email = SharedPrefs.userdata.email
val wifiOnly = SharedPrefs.connections.wifiOnly
set preference:
SharedPrefs.userdata.email = "someone#example.com"
SharedPrefs.connections.wifiOnly = true
I'm able to do so like this:
App.instance returns a Context object in the following snippet
object SharedPrefs {
val userdata by lazy { UserPreferences() }
val connections by lazy { ConnectionPreferences() }
class UserPreferences {
private val prefs: SharedPreferences = App.instance.getSharedPreferences("userdata", Context.MODE_PRIVATE)
var email: String
get() = prefs.getString("email", null)
set(value) = prefs.edit().putString("email", value).apply()
}
class ConnectionPreferences {
private val prefs: SharedPreferences = App.instance.getSharedPreferences("connections", Context.MODE_PRIVATE)
var wifyOnly: Boolean
get() = prefs.getBoolean("wifiOnly", false)
set(value) = prefs.edit().putBoolean("wifyOnly", value).apply()
}
}
The problem is that this can still be called: SharedPrefs.UserPreferences()
Can I make this constructor private to this file or object only?
You can separate the interface and the implementation class, and make the latter private to the object:
object SharedPrefs {
val userdata: UserPreferences by lazy { UserPreferencesImpl() }
interface UserPreferences {
var email: String
}
private class UserPreferencesImpl : UserPreferences {
private val prefs: SharedPreferences =
App.instance.getSharedPreferences("userdata", Context.MODE_PRIVATE)
override var email: String
get() = prefs.getString("email", null)
set(value) = prefs.edit().putString("email", value).apply()
}
// ...
}
Alternatively, if you are developing a library or you have a modular architecture, you can make use of the internal visibility modifier to restrict the visibility to the module:
class UserPreferences internal constructor() { /* ... */ }
You can try something like this
class UserPreferences private constructor()
{
// your impl
}
This is the reference