I am using Spring boot 2.0.0.Release with gradle and webflux written in Kotlin and I am trying to make ReactiveMongoDB and Elasticsearch work together, I also have JWT implementation. So to validate the AuthenticationToken I implemented ReactiveAuthenticationManager like below:
@Component
class UserAuthenticationManager(@Inject private val service: UserService,
@Inject private val encoder: PasswordEncoder) : ReactiveAuthenticationManager {
override fun authenticate(authentication: Authentication): Mono<Authentication> {
return service.findByUsernameWithPassword(authentication.name)
.publishOn(Schedulers.parallel())
.filter { u -> encoder.matches(authentication.credentials as String, u.password) }
.switchIfEmpty(Mono.defer<User> { Mono.error<User>(BadCredentialsException("Invalid Credentials")) })
.map { u -> AuthenticationToken(u.id, u.username, u.password!!, u.roles.map { SimpleGrantedAuthority(it.name) }) }
}
}
and to enable reactive mongodb repository I did this:
@Configuration
@EnableMongoAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableReactiveMongoRepositories(
basePackages = ["com.example.package.repository.**.nosql"],
excludeFilters = [
ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = [ElasticsearchRepository::class]
)
]
)
class DatabaseConfiguration
Also I configured ElasticSearchRepositories like this:
@Configuration
@EnableElasticsearchRepositories(
basePackages = ["com.example.package.repository.**.search"],
excludeFilters = [
ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = [ReactiveMongoRepository::class]
)
]
)
class ElasticsearchConfiguration {
@Bean
fun elasticsearchTemplate(client: Client, builder: Jackson2ObjectMapperBuilder): ElasticsearchTemplate {
return ElasticsearchTemplate(client, CustomEntityMapper(builder.createXmlMapper(false).build()))
}
inner class CustomEntityMapper(private val objectMapper: ObjectMapper) : EntityMapper {
init {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
}
@Throws(IOException::class)
override fun mapToString(`object`: Any): String {
return objectMapper.writeValueAsString(`object`)
}
@Throws(IOException::class)
override fun <T> mapToObject(source: String, clazz: Class<T>): T {
return objectMapper.readValue(source, clazz)
}
}
}
When I run the gradle bootRun it throws exception below:
Caused by: java.lang.IllegalStateException: availableProcessors is already set to [8], rejecting [8]
at io.netty.util.NettyRuntime$AvailableProcessorsHolder.setAvailableProcessors(NettyRuntime.java:51)
at io.netty.util.NettyRuntime.setAvailableProcessors(NettyRuntime.java:87)
at org.elasticsearch.transport.netty4.Netty4Utils.setAvailableProcessors(Netty4Utils.java:85)
at org.elasticsearch.transport.netty4.Netty4Transport.<init>(Netty4Transport.java:140)
at org.elasticsearch.transport.Netty4Plugin.lambda$getTransports$0(Netty4Plugin.java:93)
at org.elasticsearch.client.transport.TransportClient.buildTemplate(TransportClient.java:177)
at org.elasticsearch.client.transport.TransportClient.<init>(TransportClient.java:268)
at org.elasticsearch.transport.client.PreBuiltTransportClient.<init>(PreBuiltTransportClient.java:133)
at org.elasticsearch.transport.client.PreBuiltTransportClient.<init>(PreBuiltTransportClient.java:119)
at org.elasticsearch.transport.client.PreBuiltTransportClient.<init>(PreBuiltTransportClient.java:109)
at org.springframework.data.elasticsearch.client.TransportClientFactoryBean.buildClient(TransportClientFactoryBean.java:91)
at org.springframework.data.elasticsearch.client.TransportClientFactoryBean.afterPropertiesSet(TransportClientFactoryBean.java:86)
at org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration.elasticsearchClient(ElasticsearchAutoConfiguration.java:59)
at org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration$$EnhancerBySpringCGLIB$$4eba21d.CGLIB$elasticsearchClient$0(<generated>)
at org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration$$EnhancerBySpringCGLIB$$4eba21d$$FastClassBySpringCGLIB$$bc4f5c6f.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)
at org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration$$EnhancerBySpringCGLIB$$4eba21d.elasticsearchClient(<generated>)
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.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 137 common frames omitted
While debugging, I found out, setAvailableProcessors method is called twice in NettyRuntime, first for MongoReactiveAutoConfiguration and second for ElasticsearchAutoConfiguration where I get exception. Also I found out, if I remove my implementation of UserAuthenticationManager, MongoReactiveAutoConfiguration doesn't call setAvailableProcessors method so everything works fine because it is called once by ElasticsearchAutoConfiguration. Why MongoReactiveAutoConfiguration cares about ReactiveAuthenticationManager. I really confused. How can I fix this problem without removing UserAuthenticationManager implementation?
Any helps?
Solution:
With help of @RomanDzhadan I found the way of preventing reactive mongo db to check Netty available processors.
The problem was this Bean in MongoReactiveAutoConfiguration
@Bean
@ConditionalOnMissingBean
public MongoClient reactiveStreamsMongoClient(MongoProperties properties,
Environment environment,
ObjectProvider<List<MongoClientSettingsBuilderCustomizer>> builderCustomizers) {
ReactiveMongoClientFactory factory = new ReactiveMongoClientFactory(properties,
environment, builderCustomizers.getIfAvailable());
this.mongo = factory.createMongoClient(this.settings);
return this.mongo;
}
So I solved it by having my own bean definition of MongoClient for example something like this:
@Bean
fun mongoClient(): MongoClient {
logger.debug("Configuring mongo client")
return MongoClients.create(mongoProperties.determineUri())
}