Axon query response not convertible using Jackson and Kotlin - spring

I am writing a POC about Axon, SpringBoot and MongoDB using Kotlin, I have configured my serializers to use Jackson in general, events and messages and everything works as expected (Command, Event and Aggregate).
The problem begins when I am trying to perform a Query by QueryGateway I have got this error:
java.lang.IllegalArgumentException: Retrieved response [class java.util.ArrayList] is not convertible to a List of the expected response type [class com.mohammali.poc.eventsourcing.models.cardboards.CardBoardDomain]
at org.axonframework.messaging.responsetypes.MultipleInstancesResponseType.convert(MultipleInstancesResponseType.java:113) ~[axon-messaging-4.5.15.jar:4.5.15]
at org.axonframework.messaging.responsetypes.MultipleInstancesResponseType.convert(MultipleInstancesResponseType.java:44) ~[axon-messaging-4.5.15.jar:4.5.15]
at org.axonframework.messaging.responsetypes.ConvertingResponseMessage.getPayload(ConvertingResponseMessage.java:85) ~[axon-messaging-4.5.15.jar:4.5.15]
at org.axonframework.queryhandling.DefaultQueryGateway.lambda$query$1(DefaultQueryGateway.java:87) ~[axon-messaging-4.5.15.jar:4.5.15]
at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:718) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147) ~[na:na]
at org.axonframework.axonserver.connector.query.AxonServerQueryBus$ResponseProcessingTask.run(AxonServerQueryBus.java:761) ~[axon-server-connector-4.5.15.jar:4.5.15]
I have tracked the issue and I believe the problem is the response type comes always as java.util.ArrayList without the generic type
here is the implementation:
Query Handler
package com.mohammali.poc.eventsourcing.queryhandler.query
import com.mohammali.poc.eventsourcing.models.cardboards.CardBoardDomain
import com.mohammali.poc.eventsourcing.models.cardboards.QueryFindAllCardBoard
import com.mohammali.poc.eventsourcing.queryhandler.data.MongoCardBoardRepository
import org.axonframework.config.ProcessingGroup
import org.axonframework.queryhandling.QueryHandler
import org.springframework.stereotype.Component
#Component
#ProcessingGroup("cardboard-query")
class CardBoardQueryHandler(
private val repository: MongoCardBoardRepository
) {
#QueryHandler
fun on(query: QueryFindAllCardBoard): List<CardBoardDomain> =
repository.findAll().map {
CardBoardDomain(it.id!!, it.name!!, it.width!!, it.height!!)
}
}
Query Caller or QueryGateway
package com.mohammali.poc.eventsourcing.gateway.usecases
import com.mohammali.poc.eventsourcing.models.cardboards.CardBoardDomain
import com.mohammali.poc.eventsourcing.models.cardboards.QueryFindAllCardBoard
import org.axonframework.extensions.kotlin.queryMany
import org.axonframework.queryhandling.QueryGateway
import org.springframework.stereotype.Service
#Service
class CardBoardUseCase(
private val queryGateway: QueryGateway
) {
fun findAll(): List<CardBoardDomain> =
queryGateway.queryMany<CardBoardDomain, QueryFindAllCardBoard>(QueryFindAllCardBoard()).get()
}
Domain I am using
package com.mohammali.poc.eventsourcing.models.cardboards
import com.mohammali.poc.eventsourcing.models.Id
data class CardBoardDomain(
val id: Id,
val name: String,
val width: Double,
val height: Double
)
Custom Serializer
#Bean
#Qualifier("mainObjectMapper")
#Primary
fun createMapper(): ObjectMapper {
val builder = Jackson2ObjectMapperBuilder()
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
val o = builder.build<ObjectMapper>()
.registerKotlinModule()
.registerModule(
SimpleModule()
.addSerializer(Id::class.javaObjectType, IdJsonSerializer())
.addSerializer(Id::class.javaPrimitiveType, IdJsonSerializer())
.addDeserializer(Id::class.javaObjectType, IdJsonDeserializer())
.addDeserializer(Id::class.javaPrimitiveType, IdJsonDeserializer())
)
return o
}
#Bean
#Primary
fun axonJacksonSerializer(objectMapper: ObjectMapper): Serializer =
JacksonSerializer.builder()
.objectMapper(objectMapper)
.build()
Update
I have tried adding defaultTyping to JacksonSerializer suggested by Mitchell Herrijgers in this answer but I get a new error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object
at [Source: (byte[])"[{"id":"1015764681047937024","name":"test","width":10.0,"height":10.0}]"; line: 1, column: 2] (through reference chain: java.util.ArrayList[0])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:1939) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.DeserializationContext.reportWrongTokenException(DeserializationContext.java:1673) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._locateTypeId(AsArrayTypeDeserializer.java:141) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:96) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromAny(AsArrayTypeDeserializer.java:71) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:781) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:357) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2051) ~[jackson-databind-2.13.3.jar:2.13.3]
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1529) ~[jackson-databind-2.13.3.jar:2.13.3]
at org.axonframework.serialization.json.JacksonSerializer.deserialize(JacksonSerializer.java:201) ~[axon-messaging-4.5.15.jar:4.5.15]
at org.axonframework.serialization.LazyDeserializingObject.getObject(LazyDeserializingObject.java:102) ~[axon-messaging-4.5.15.jar:4.5.15]
at org.axonframework.axonserver.connector.query.GrpcBackedResponseMessage.getPayload(GrpcBackedResponseMessage.java:99) ~[axon-server-connector-4.5.15.jar:4.5.15]
at org.axonframework.messaging.responsetypes.ConvertingResponseMessage.getPayload(ConvertingResponseMessage.java:85) ~[axon-messaging-4.5.15.jar:4.5.15]
at org.axonframework.queryhandling.DefaultQueryGateway.lambda$query$1(DefaultQueryGateway.java:87) ~[axon-messaging-4.5.15.jar:4.5.15]
at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:718) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147) ~[na:na]
at org.axonframework.axonserver.connector.query.AxonServerQueryBus$ResponseProcessingTask.run(AxonServerQueryBus.java:761) ~[axon-server-connector-4.5.15.jar:4.5.15]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
The new config
#Bean
#Qualifier("mainObjectMapper")
#Primary
fun createMapper(): ObjectMapper {
val builder = Jackson2ObjectMapperBuilder()
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
return builder.build<ObjectMapper>()
.registerKotlinModule()
.registerModule(
SimpleModule()
.addSerializer(Id::class.javaObjectType, IdJsonSerializer())
.addSerializer(Id::class.javaPrimitiveType, IdJsonSerializer())
.addDeserializer(Id::class.javaObjectType, IdJsonDeserializer())
.addDeserializer(Id::class.javaPrimitiveType, IdJsonDeserializer())
)
.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY)
}
#Bean
#Primary
fun axonJacksonSerializer(objectMapper: ObjectMapper): Serializer =
JacksonSerializer.builder()
.objectMapper(objectMapper)
.defaultTyping()
.build()
Also I am using this config:
axon:
serializer:
general: jackson
events: jackson
messages: jackson

Jackson loses its type information when using the default ObjectMapper settings. Axon has a method to easily configure including list information into the JSON. You can adjust your bean definition to this:
#Bean
#Primary
fun axonJacksonSerializer(objectMapper: ObjectMapper): Serializer =
JacksonSerializer.builder()
.objectMapper(objectMapper)
.defaultTyping()
.build()
Calling the defaultTyping() on the builder will set the appropriate setting on the ObjectMapper. You can find more information on the topic here

Related

Why spring-boot with Kafka failed to start?

There is spring-boot application with kafka dependecy,
there are two Kafka topics and need to read messages from them
tacocloud.orders.topic
tacocloud.tacos.topic
And already successful sent messages in it
Configured kafka config for listen this topics like this
package com.example.tacocloud.config;
import com.example.tacocloud.model.jpa.Order;
import com.example.tacocloud.model.jpa.Taco;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.listener.ContainerProperties;
import org.springframework.kafka.listener.KafkaMessageListenerContainer;
import org.springframework.kafka.listener.MessageListener;
import java.util.HashMap;
import java.util.Map;
#Slf4j
#Configuration
#EnableKafka
#EnableConfigurationProperties
public class KafkaConfig {
// Order
#Bean
#ConfigurationProperties("spring.kafka.consumer.order")
public Map<String, Object> orderConsumerConfig() {
return new HashMap<>();
}
#Bean
public DefaultKafkaConsumerFactory<Object, Order> orderConsumerFactory(#Qualifier("orderConsumerConfig")
Map<String, Object> orderConsumerConfig) {
return new DefaultKafkaConsumerFactory<>(orderConsumerConfig);
}
#Bean
public ConcurrentKafkaListenerContainerFactory<String, Order> order1KafkaMessageListenerContainer(
#Qualifier("orderConsumerConfig") Map<String, Object> orderConsumerConfig,
#Qualifier("orderConsumerFactory") DefaultKafkaConsumerFactory orderConsumerFactory) {
ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory();
factory.setConsumerFactory(orderConsumerFactory);
return factory;
}
// Taco
#Bean
#ConfigurationProperties("spring.kafka.consumer.taco")
public Map<String, Object> tacoConsumerConfig() {
return new HashMap<>();
}
#Bean
public DefaultKafkaConsumerFactory tacoConsumerFactory(
#Qualifier("tacoConsumerConfig") Map<String, Object> tacoConsumerConfig) {
return new DefaultKafkaConsumerFactory<>(tacoConsumerConfig);
}
#Bean
public ConcurrentKafkaListenerContainerFactory tacoConcurrentMessageListenerContainer(
#Qualifier("tacoConsumerConfig") Map<String, Object> tacoConsumerConfig,
#Qualifier("tacoConsumerFactory") DefaultKafkaConsumerFactory tacoConsumerFactory) {
ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory();
factory.setConsumerFactory(tacoConsumerFactory);
return factory;
}
}
So, there are two DefaultKafkaConsumerFactory and two ConcurrentKafkaListenerContainerFactory
Aften that, created a service with #KafkaListener log messages:
package com.example.tacocloud.service;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
#Service
#EnableKafka
public class KafkaService {
#KafkaListener(topics = "tacocloud.orders.topic", groupId = "one")
public void order() {
System.out.println("Order");
}
#KafkaListener(topics ="tacocloud.tacos.topic", groupId = "two")
public void taco() {
System.out.println("Taco");
}
}
application.yml file
spring:
kafka:
consumer:
order:
topic: tacocloud.orders.topic
"[bootstrap.servers]": localhost:29888
"[key.deserializer]": org.apache.kafka.common.serialization.StringDeserializer
"[value.deserializer]": com.example.tacocloud.model.serialization.OrderDeserializer
template:
"[default.topic]": tacocloud.orders.topic
taco:
topic: tacocloud.tacos.topic
"[bootstrap.servers]": localhost:29888
"[key.deserializer]": org.apache.kafka.common.serialization.StringDeserializer
"[value.deserializer]": com.example.tacocloud.model.serialization.TacoDeserializer
template:
"[default.topic]": tacocloud.tacos.topic
But, when I start my spring-boot application, I see error message(((
2022-04-15 21:38:35.918 ERROR 13888 --- [ restartedMain]
o.s.boot.SpringApplication : Application run failed
org.springframework.context.ApplicationContextException: Failed to
start bean
'org.springframework.kafka.config.internalKafkaListenerEndpointRegistry';
nested exception is org.apache.kafka.common.config.ConfigException:
Missing required configuration "key.deserializer" which has no default
value. at
org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181)
~[spring-context-5.3.16.jar:5.3.16] at
org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54)
~[spring-context-5.3.16.jar:5.3.16] at
org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356)
~[spring-context-5.3.16.jar:5.3.16] at
java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] at
org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155)
~[spring-context-5.3.16.jar:5.3.16] at
org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123)
~[spring-context-5.3.16.jar:5.3.16] at
org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935)
~[spring-context-5.3.16.jar:5.3.16] at
org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586)
~[spring-context-5.3.16.jar:5.3.16] at
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
~[spring-boot-2.6.4.jar:2.6.4] at
org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740)
~[spring-boot-2.6.4.jar:2.6.4] at
org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415)
~[spring-boot-2.6.4.jar:2.6.4] at
org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
~[spring-boot-2.6.4.jar:2.6.4] at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1312)
~[spring-boot-2.6.4.jar:2.6.4] at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)
~[spring-boot-2.6.4.jar:2.6.4] at
com.example.tacocloud.TacoCloudApplication.main(TacoCloudApplication.java:10)
~[classes/:na] at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native
Method) ~[na:na] at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
~[na:na] at
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[na:na] at
java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at
org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
~[spring-boot-devtools-2.6.4.jar:2.6.4] Caused by:
org.apache.kafka.common.config.ConfigException: Missing required
configuration "key.deserializer" which has no default value. at
org.apache.kafka.common.config.ConfigDef.parseValue(ConfigDef.java:493)
~[kafka-clients-2.8.0.jar:na] at
org.apache.kafka.common.config.ConfigDef.parse(ConfigDef.java:483)
~[kafka-clients-2.8.0.jar:na] at
org.apache.kafka.common.config.AbstractConfig.(AbstractConfig.java:108)
~[kafka-clients-2.8.0.jar:na] at
org.apache.kafka.common.config.AbstractConfig.(AbstractConfig.java:129)
~[kafka-clients-2.8.0.jar:na] at
org.apache.kafka.clients.consumer.ConsumerConfig.(ConsumerConfig.java:640)
~[kafka-clients-2.8.0.jar:na] at
org.apache.kafka.clients.consumer.KafkaConsumer.(KafkaConsumer.java:665)
~[kafka-clients-2.8.0.jar:na] at
org.springframework.kafka.core.DefaultKafkaConsumerFactory.createRawConsumer(DefaultKafkaConsumerFactory.java:416)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.core.DefaultKafkaConsumerFactory.createKafkaConsumer(DefaultKafkaConsumerFactory.java:384)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.core.DefaultKafkaConsumerFactory.createConsumerWithAdjustedProperties(DefaultKafkaConsumerFactory.java:360)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.core.DefaultKafkaConsumerFactory.createKafkaConsumer(DefaultKafkaConsumerFactory.java:327)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.core.DefaultKafkaConsumerFactory.createConsumer(DefaultKafkaConsumerFactory.java:304)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.(KafkaMessageListenerContainer.java:758)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.listener.KafkaMessageListenerContainer.doStart(KafkaMessageListenerContainer.java:344)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.listener.AbstractMessageListenerContainer.start(AbstractMessageListenerContainer.java:442)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.listener.ConcurrentMessageListenerContainer.doStart(ConcurrentMessageListenerContainer.java:209)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.listener.AbstractMessageListenerContainer.start(AbstractMessageListenerContainer.java:442)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.config.KafkaListenerEndpointRegistry.startIfNecessary(KafkaListenerEndpointRegistry.java:331)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.kafka.config.KafkaListenerEndpointRegistry.start(KafkaListenerEndpointRegistry.java:276)
~[spring-kafka-2.8.3.jar:2.8.3] at
org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178)
~[spring-context-5.3.16.jar:5.3.16] ... 19 common frames omitted
Process finished with exit code 0
Thank you for a sample.
So, I opened it locally and placed a break point into this bean definition:
#Bean
public DefaultKafkaConsumerFactory<Object, Order> orderConsumerFactory(#Qualifier("orderConsumerConfig")
Map<String, Object> orderConsumerConfig) {
return new DefaultKafkaConsumerFactory<Object, Order>(orderConsumerConfig);
}
That orderConsumerConfig map looks like this:
orderConsumerConfig = {LinkedHashMap#8587} size = 1
"orderConsumerConfig" -> {HashMap#8600} size = 5
key = "orderConsumerConfig"
value = {HashMap#8600} size = 5
"key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer"
"template" -> {LinkedHashMap#8611} size = 1
"topic" -> "tacocloud.orders.topic"
"bootstrap.servers" -> "localhost:29888"
"value.deserializer" -> "sample.kafka.serializer.OrderDeserializer"
so, that's indeed not a surprise that your KafkaConsumer is not initialized properly. Your target map config is hidden under that orderConsumerConfig entry of this injected map.
Would you mind to share with me from where did you take an idea of the #ConfigurationProperties on the Map bean? And how that supposed to be used as dependency injection?
I wanted to do something similar (configure multiple ConsumerFactories) based on properties.
I used #ConfigurationProperties to create a Map<String,String> instead of Map<String,Object> and then added the items of that map into a new Map<String,Object>. Not sure why Spring-Boot loaded the Map<String,Object> that way.
#Bean
#ConfigurationProperties("taco-cart.kafka")
public Map<String, String> tacoCartKafkaProperties() {
return new HashMap<>();
}
#Bean
public ConsumerFactory<String, TacoCart> tacoCartConsumerFactory(#Qualifier("tacoCartKafkaProperties") Map<String, String> tacoCartKafkaProperties) {
// Convert map.
Map<String, Object> config = new HashMap<>();
config.putAll(tacoCartKafkaProperties);
return new DefaultKafkaConsumerFactory<>(config);
}

Cannot disable spring-data-mongodb-reactive autoconfiguration in spring-boot

No matter what I try, I cannot disable auto-configuration of spring-data-mongodb-reactive.
Properties
spring:
profiles: dev
data:
mongodb:
uri: "mongodb://user:pw#some-ip.amazonaws.com:27017/my-db"
repositories:
type: reactive
authentication-database: admin
Repository
#Repository
interface IMembersRepository: ReactiveMongoRepository<Member, String> {}
MongoConfig
#Configuration
#EnableReactiveMongoRepositories(basePackages = ["com.my.package.repository"])
class MongoConfig : AbstractReactiveMongoConfiguration() {
override fun reactiveMongoClient(): MongoClient = mongoClient()
override fun getDatabaseName(): String = "my-db"
#Bean()
fun mongoClient() = MongoClients.create()
#Bean()
override fun reactiveMongoTemplate() = ReactiveMongoTemplate(mongoClient(), databaseName)
}
AppConfig
#Configuration
#EnableWebFlux
#ComponentScan("com.my.package")
class AppConfig: WebFluxConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("api/**")
}
}
SpringBootApplication
#SpringBootApplication(exclude = [
MongoReactiveAutoConfiguration::class,
MongoReactiveDataAutoConfiguration::class,
MongoReactiveRepositoriesAutoConfiguration::class,
MongoAutoConfiguration::class,
MongoDataAutoConfiguration::class,
MongoRepositoriesAutoConfiguration::class,
EmbeddedMongoAutoConfiguration::class
])
class AstridServerApplication
fun main(args: Array<String>) {
runApplication<AstridServerApplication>(*args)
}
As you can see, I even went as far as disabling all available MongoDB auto-configs, and Boot still tries to establish connection to a local instance, which I do not have. But I did also try different combinations.
[localhost:27017] org.mongodb.driver.cluster : Exception in monitor thread while connecting to server localhost:27017
com.mongodb.MongoSocketOpenException: Exception opening socket
at com.mongodb.internal.connection.AsynchronousSocketChannelStream$OpenCompletionHandler.failed(AsynchronousSocketChannelStream.java:117) ~[mongodb-driver-core-3.11.2.jar:na]
at java.base/sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:129) ~[na:na]
at java.base/sun.nio.ch.Invoker.invokeDirect(Invoker.java:158) ~[na:na]
at java.base/sun.nio.ch.Invoker.invoke(Invoker.java:186) ~[na:na]
at java.base/sun.nio.ch.Invoker.invoke(Invoker.java:298) ~[na:na]
at java.base/sun.nio.ch.WindowsAsynchronousSocketChannelImpl$ConnectTask.failed(WindowsAsynchronousSocketChannelImpl.java:308) ~[na:na]
at java.base/sun.nio.ch.Iocp$EventHandlerTask.run(Iocp.java:389) ~[na:na]
at java.base/sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
Caused by: java.io.IOException: The remote computer refused the network connection
at java.base/sun.nio.ch.Iocp.translateErrorToIOException(Iocp.java:299) ~[na:na]
... 5 common frames omitted
The client is connecting to the default address. I would focus on understanding why your config file isn't taking effect rather than on "disabling auto-configuration". The behavior you are seeing is consistent with the client not receiving any external configuration and using its built-in defaults.

Spring data redis override default serializer

I am trying to create a RedisTemplate bean which will have the updated value serializer to serialize an object in JSON format in redis.
#Configuration
class RedisConfig {
#Bean(name = ["redisTemplate"])
#Primary
fun template(factory: RedisConnectionFactory): RedisTemplate<Any, Any> {
val template = RedisTemplate<Any, Any>()
template.connectionFactory = factory
template.valueSerializer = Jackson2JsonRedisSerializer(Object::class.java)
template.afterPropertiesSet()
return template
}
}
As per my understanding, spring should use the JSON serializer to serialize the object returned by the methods marked with Cacheable annotation. Despite this configuration, spring seems to be using the default Java serializer as this exception confirms this fact.
java.io.NotSerializableException: en.prateekj.vds.dto.Task
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at java.util.ArrayList.writeObject(ArrayList.java:766)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1128)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:46)
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:63)
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:35)
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:94)
at org.springframework.data.redis.serializer.DefaultRedisElementWriter.write(DefaultRedisElementWriter.java:43)
at org.springframework.data.redis.serializer.RedisSerializationContext$SerializationPair.write(RedisSerializationContext.java:219)
at org.springframework.data.redis.cache.RedisCache.serializeCacheValue(RedisCache.java:238)
at org.springframework.data.redis.cache.RedisCache.put(RedisCache.java:144)
at org.springframework.cache.interceptor.AbstractCacheInvoker.doPut(AbstractCacheInvoker.java:87)
at org.springframework.cache.interceptor.CacheAspectSupport$CachePutRequest.apply(CacheAspectSupport.java:770)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:398)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:314)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
Am I missing any configuration or something by which spring is not able to determine what RedisTemplate to use?
You have probably solved it meanwhile, but for further answer seekers.
According to spring data redis reference:
By default, RedisCache and RedisTemplate are configured to use Java native serialization.
From stacktrace I can see that you are actually using Redis for caching, so you need to configure RedisCache and not RedisTemplate. RedisCache is not picking up your #Bean because it is not using RedisTemplate internally.
Example how you can do it in Java:
#EnableCaching
#Configuration
public class CacheConfig {
#Bean
#Primary
public RedisCacheConfiguration defaultCacheConfig(ObjectMapper objectMapper) {
return RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));
}
}

Kotlin - lateinit TestRestTemplate not initializing for integration tests

I have a spring-boot app in development using kotlin - overall things are going well. (spring 1.5.6.RELEASE, kotlin 1.1.4-3)
Anyway, I was adding my first controller test after reviewing some example code, and am running into this annoying error:
kotlin.UninitializedPropertyAccessException: lateinit property restTemplate has not been initialized
kotlin.UninitializedPropertyAccessException: lateinit property testRestTemplate has not been initialized
at com.thingy.controllers.ProductSetControllerTest.getTestRestTemplate(ProductSetControllerTest.kt:16)
at com.thingy.controllers.ProductSetControllerTest.testGet(ProductSetControllerTest.kt:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
at org.testng.TestRunner.privateRun(TestRunner.java:767)
at org.testng.TestRunner.run(TestRunner.java:617)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
at org.testng.SuiteRunner.run(SuiteRunner.java:240)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1198)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1123)
at org.testng.TestNG.run(TestNG.java:1031)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:123)
Here's the test class
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.test.context.junit4.SpringRunner
import org.testng.Assert
import org.testng.annotations.Test
#RunWith(SpringRunner::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ProductSetControllerTest {
#Autowired
lateinit var restTemplate: TestRestTemplate
#Test
fun testGet() {
val forObject = restTemplate.getForObject("/", String::class.java)
Assert.assertEquals(forObject, "gettest")
}
}
Some addl things I've tried:
- ensured I'm not using deprecated TestRestTemplate
- tried using setter injection instead of field injection, but was a waste of time.
- disabled kotlin compiler plugins
Spring Boot does not automatically instantiate TestRestTemplate bean(duh!).
You need to define it yourself and then you will be able to use it.
#Bean
open fun restTemplate(): TestRestTemplate {
return TestRestTemplate()
}

How do you use both Spring Data JPA and Spring Data Elasticsearch repositories on the same domain class in a Spring Boot application?

I'm trying to use both Spring Data JPA and Spring Data Elasticsearch on the same domain object but it doesn't work.
When I tried to run a simple test, I got the following exception:
org.springframework.data.mapping.PropertyReferenceException: No
property index found for type Person! at
org.springframework.data.mapping.PropertyPath.(PropertyPath.java:75)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:327)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:307)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:270)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:241)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.repository.query.parser.Part.(Part.java:76)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.repository.query.parser.PartTree$OrPart.(PartTree.java:235)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.repository.query.parser.PartTree$Predicate.buildTree(PartTree.java:373)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.repository.query.parser.PartTree$Predicate.(PartTree.java:353)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.repository.query.parser.PartTree.(PartTree.java:84)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.jpa.repository.query.PartTreeJpaQuery.(PartTreeJpaQuery.java:61)
~[spring-data-jpa-1.9.0.RELEASE.jar:na] at
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:95)
~[spring-data-jpa-1.9.0.RELEASE.jar:na] at
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:206)
~[spring-data-jpa-1.9.0.RELEASE.jar:na] at
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:73)
~[spring-data-jpa-1.9.0.RELEASE.jar:na] at
org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.(RepositoryFactorySupport.java:408)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:206)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:251)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:237)
~[spring-data-commons-1.11.0.RELEASE.jar:na] at
org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:92)
~[spring-data-jpa-1.9.0.RELEASE.jar:na] at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
~[spring-beans-4.2.1.RELEASE.jar:4.2.1.RELEASE] at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
~[spring-beans-4.2.1.RELEASE.jar:4.2.1.RELEASE] ... 43 common frames
omitted
They work when disabling either one.
The project is based on Spring Boot 1.3.0.M5.
This is a sample project reproducing the situation:
https://github.com/izeye/spring-boot-throwaway-branches/tree/data-jpa-and-elasticsearch
Repositories in Spring Data are datasource agnostic, meaning that JpaRepository and ElasticsearchRepository both roll up into Repository interface. When this is the case, then auto-configuration of Spring Boot will cause Spring Data JPA to try and configure a bean for each repository in the project that inherits any Spring Data Commons base repository.
To fix this problem you need to move your JPA repository and Elasticsearch repository to separate packages and make sure to annotate your #SpringBootApplication application class with:
#EnableJpaRepositories
#EnableElasticsearchRepositories
Then you need to specify where the repositories are for each enable annotation. This ends up looking like:
#SpringBootApplication
#EnableJpaRepositories("com.izeye.throwaway.data")
#EnableElasticsearchRepositories("com.izeye.throwaway.indexing")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Then your application will be able to disambiguate which repositories are intended for which Spring Data project.
You can use like this:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ElasticsearchCrudRepository.class))
#EnableElasticsearchRepositories(includeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ElasticsearchCrudRepository.class))
public class DataConfiguration {
...
}
Or in SpringBoot:
#SpringBootApplication
#EnableJpaRepositories(excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ElasticsearchCrudRepository.class))
#EnableElasticsearchRepositories(includeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ElasticsearchCrudRepository.class))
public class MyApplication {
...
}

Resources